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



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

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

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

在C#?

中有这样的问题

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() Overload使用提供的值作为参数,您可以将其视为替代方案。

我发现创建类似的扩展方法与ConcurrentDictionary上的方法紧密相吻合非常有用。

我会说清洁器代码看起来像这样:

var key = arr[i];
var hasKey = dictionary.ContainsKey(key);
if (!hasKey)
    dictionary.Add(key, new A());
var itemToUse = dictionary[key];
itemToUse.Push(10);

尽管在我看来,您正在寻找 shorter 的东西。我想您真正问的是一种做短途的方法:

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

我认为上面的代码可以说明更多有关意图的信息,但是如果您想要不同的东西,我可以考虑以下两个解决方案。

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

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);

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

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);

我必须警告您一些东西,这个字典的衍生物隐藏了索引操作员。而且,由于使用IDictionary作为成员类型是一种常见的实践(例如private IDictionary<string, A> dictionary作为成员),因此您不能不铸造而使用过载版本。因此,每次您要使用过载索引器时,要么将变量施放为默认数字,要么具有以下新字典的接口:

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

让您的会员使用它作为定义类型:

private IDefaultDictionary<string, A> dictionary;

,但这也意味着作为具体类,您现在必须使用默认数字,这就是权衡。

最新更新