【问题标题】:Dictionary returning a default value if the key does not exist [duplicate]如果键不存在,则字典返回默认值[重复]
【发布时间】:2011-02-05 18:52:21
【问题描述】:

我发现自己现在在我的代码中经常使用当前模式

var dictionary = new Dictionary<type, IList<othertype>>();
// Add stuff to dictionary

var somethingElse = dictionary.ContainsKey(key) ? dictionary[key] : new List<othertype>();
// Do work with the somethingelse variable

有时

var dictionary = new Dictionary<type, IList<othertype>>();
// Add stuff to dictionary

IList<othertype> somethingElse;
if(!dictionary.TryGetValue(key, out somethingElse) {
    somethingElse = new List<othertype>();
}

这两种方式都感觉很迂回。我真正想要的是像

dictionary.GetValueOrDefault(key)

现在,我可以为字典类编写一个扩展方法来为我做这件事,但我想我可能会遗漏一些已经存在的东西。那么,有没有一种方法可以在不为字典编写扩展方法的情况下以一种更“容易上手”的方式做到这一点?

【问题讨论】:

  • 不确定为什么以下所有答案都如此复杂。只需使用合并运算符:string valFromDict = someDict["someKey"] ?? "someDefaultVal";
  • @DylanHunt 但这对值类型不起作用。 ;)
  • 另请注意,使用[] 运算符还可能产生不希望的副作用,即在字典中添加不存在的值。

标签: c# collections dictionary


【解决方案1】:

TryGetValue 已经将类型的默认值分配给字典,因此您可以使用:

dictionary.TryGetValue(key, out value);

并忽略返回值。然而,真正只返回default(TValue),而不是一些自定义默认值(更有用的是,执行委托的结果)。没有什么比框架更强大的了。我会建议两种扩展方法:

public static TValue GetValueOrDefault<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary,
    TKey key,
    TValue defaultValue)
{
    return dictionary.TryGetValue(key, out var value) ? value : defaultValue;
}

public static TValue GetValueOrDefault<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary,
    TKey key,
    Func<TValue> defaultValueProvider)
{
    return dictionary.TryGetValue(key, out var value) ? value : defaultValueProvider();
}

(当然,您可能希望进行参数检查:)

【讨论】:

  • @ProfK:无论如何,您都可以将密钥与null 进行比较;当 TKey 是不可为空的值类型时,它总是返回 false。就我个人而言,我认为我不想这样做 - 根据我的经验,空键 几乎 总是代表一个错误。
  • @ProfK:是的 - 为您的特定案例更改代码是有意义的,但我不想一般建议:)
  • 谢谢。第三个参数的默认值怎么样? TValue defaultValue = default(TValue)
  • 鉴于 Jon 的回答,我被困在如何实际调用他的扩展方法上(以前从未这样做过),所以我偶然发现了这篇 MS 文章:msdn.microsoft.com/en-us/library/bb311042.aspx
  • 在以后的版本中,您可以使用 System.Collections.Generic,它提供 GetValueOrDefault(key, defaultValue)。
【解决方案2】:

我确实喜欢扩展方法,但是当我需要默认值时,我不时使用一个简单的类来处理字典。

我希望这只是基本 Dictionary 类的一部分。

public class DictionaryWithDefault<TKey, TValue> : Dictionary<TKey, TValue>
{
  TValue _default;
  public TValue DefaultValue {
    get { return _default; }
    set { _default = value; }
  }
  public DictionaryWithDefault() : base() { }
  public DictionaryWithDefault(TValue defaultValue) : base() {
    _default = defaultValue;
  }
  public new TValue this[TKey key]
  {
    get { 
      TValue t;
      return base.TryGetValue(key, out t) ? t : _default;
    }
    set { base[key] = value; }
  }
}

但要小心。通过子类化和使用new(因为override 在本机Dictionary 类型上不可用),如果DictionaryWithDefault 对象向上转换为普通Dictionary,则调用索引器将使用基本Dictionary 实现(如果丢失则抛出异常)而不是子类的实现。

【讨论】:

  • 使用 trygetvalue 更好..
  • trygetvalue 不允许您指定默认值,即对于字符串值,您可能希望它返回 "" 而不是 null
  • @roberocity 是的性能优势,请参阅:stackoverflow.com/questions/9382681/…
  • 上面的代码有一个bug。它将始终返回 0 作为默认值,因为 TryGetValue 将“t”设置为 0。更改为; T值 t; if (!TryGetValue(key, out t)) { t = m_default; } 返回 t;
  • 我认为在这种情况下从Dictionary 继承是不明智的:通过使用new 关键字,您隐藏了非虚拟Item 属性。如果有人要从Dictionary 引用类型而不是DictionaryWithDefault 访问它,则将调用DictionaryItem 属性,而不是您的。
【解决方案3】:

我创建了一个DefaultableDictionary 来满足您的要求!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace DefaultableDictionary {
    public class DefaultableDictionary<TKey, TValue> : IDictionary<TKey, TValue> {
        private readonly IDictionary<TKey, TValue> dictionary;
        private readonly TValue defaultValue;

        public DefaultableDictionary(IDictionary<TKey, TValue> dictionary, TValue defaultValue) {
            this.dictionary = dictionary;
            this.defaultValue = defaultValue;
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
            return dictionary.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        public void Add(KeyValuePair<TKey, TValue> item) {
            dictionary.Add(item);
        }

        public void Clear() {
            dictionary.Clear();
        }

        public bool Contains(KeyValuePair<TKey, TValue> item) {
            return dictionary.Contains(item);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
            dictionary.CopyTo(array, arrayIndex);
        }

        public bool Remove(KeyValuePair<TKey, TValue> item) {
            return dictionary.Remove(item);
        }

        public int Count {
            get { return dictionary.Count; }
        }

        public bool IsReadOnly {
            get { return dictionary.IsReadOnly; }
        }

        public bool ContainsKey(TKey key) {
            return dictionary.ContainsKey(key);
        }

        public void Add(TKey key, TValue value) {
            dictionary.Add(key, value);
        }

        public bool Remove(TKey key) {
            return dictionary.Remove(key);
        }

        public bool TryGetValue(TKey key, out TValue value) {
            if (!dictionary.TryGetValue(key, out value)) {
                value = defaultValue;
            }

            return true;
        }

        public TValue this[TKey key] {
            get
            {
                try
                {
                    return dictionary[key];
                } catch (KeyNotFoundException) {
                    return defaultValue;
                }
            }

            set { dictionary[key] = value; }
        }

        public ICollection<TKey> Keys {
            get { return dictionary.Keys; }
        }

        public ICollection<TValue> Values {
            get
            {
                var values = new List<TValue>(dictionary.Values) {
                    defaultValue
                };
                return values;
            }
        }
    }

    public static class DefaultableDictionaryExtensions {
        public static IDictionary<TKey, TValue> WithDefaultValue<TValue, TKey>(this IDictionary<TKey, TValue> dictionary, TValue defaultValue ) {
            return new DefaultableDictionary<TKey, TValue>(dictionary, defaultValue);
        }
    }
}

这个项目是一个 IDictionary 对象的简单装饰器和一个扩展方法,使其易于使用。

DefaultableDictionary 将允许围绕字典创建一个包装器,该包装器在尝试访问不存在的键或枚举 IDictionary 中的所有值时提供默认值。

示例:var dictionary = new Dictionary&lt;string, int&gt;().WithDefaultValue(5);

Blog post 的用法也是如此。

【讨论】:

  • 请在您的回答中直接提供支持 github 链接的上下文,否则可能会被删除。
  • 如果您不删除提供清晰的链接,将会很有帮助。
  • 我将 index getter 写为:public TValue this[TKey key] { get { TValue value; TryGetValue(key, out value); return value; } 以避免异常处理。
  • 我喜欢您使用扩展方法来创建默认字典,而不是要求使用您的构造函数(就像我一样)。
  • 基于异常的流控制...哎呀!这是一个严重的反模式。请更改您的索引器实现以使用TryGetValue 而不是try ... catch
【解决方案4】:

不,不存在这样的东西。扩展方法是要走的路,你的名字(GetValueOrDefault)是个不错的选择。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-26
    • 2016-02-29
    • 1970-01-01
    • 1970-01-01
    • 2014-06-17
    • 2012-03-22
    • 1970-01-01
    • 2013-03-02
    相关资源
    最近更新 更多