如何获取null而不是按键访问字典值的KeyNotFoundException?

hor*_*rgh 68 c# dictionary

在某些情况下,当字典中没有这样的键时,对于我来说,有一个简短的,可读的方式来获取null而不是KeyNotFoundException按键访问字典值,这似乎是有用的.

我想到的第一件事是扩展方法:

public static U GetValueByKeyOrNull<T, U>(this Dictionary<T, U> dict, T key)
        where U : class //it's acceptable for me to have this constraint
{
    if (dict.ContainsKey(key))
        return dict[key];
    else 
        //it could be default(U) to use without U class constraint
        //however, I didn't need this.
        return null; 
}
Run Code Online (Sandbox Code Playgroud)

但是当你写下这样的东西时,它实际上并不是很短暂的说法:

string.Format("{0}:{1};{2}:{3}",                                                 
              dict.GetValueByKeyOrNull("key1"),
              dict.GetValueByKeyOrNull("key2"),
              dict.GetValueByKeyOrNull("key3"),
              dict.GetValueByKeyOrNull("key4"));
Run Code Online (Sandbox Code Playgroud)

我会说,有一些接近基本语法的东西要好得多:dict["key4"].

然后我想出了一个带有private字典字段的类的想法,它暴露了我需要的功能:

public class MyDictionary<T, U> //here I may add any of interfaces, implemented
                                //by dictionary itself to get an opportunity to,
                                //say, use foreach, etc. and implement them
                                // using the dictionary field.
        where U : class
{
    private Dictionary<T, U> dict;

    public MyDictionary()
    {
        dict = new Dictionary<T, U>();
    }

    public U this[T key]
    {
        get
        {
            if (dict.ContainsKey(key))
                return dict[key];
            else
                return null;
        }
        set
        {
            dict[key] = value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,基本行为的微小变化似乎有点开销.

还有一种解决方法可能是Func在当前上下文中定义a ,如下所示:

Func<string, string> GetDictValueByKeyOrNull = (key) =>
{
    if (dict.ContainsKey(key))
        return dict[key];
    else
        return null;
};
Run Code Online (Sandbox Code Playgroud)

所以可以像GetDictValueByKeyOrNull("key1").

请你再给我一些建议或帮助选择一个更好的建议?

Jar*_*d G 55

这是我个人库中的解决方案,作为扩展方法实现.我只发布它,因为它是从字典界面实现的,并允许传入可选的默认值.

履行

public static TV GetValue<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue = default(TV))
{
    TV value;
    return dict.TryGetValue(key, out value) ? value : defaultValue;
}
Run Code Online (Sandbox Code Playgroud)

用法

 MyDictionary.GetValue("key1");
 MyDictionary.GetValue("key2", -1);
 MyDictionary.GetValue("key3")?.SomeMethod();
Run Code Online (Sandbox Code Playgroud)

  • 如果你问我,最好的解决方案.现在甚至可以在C#7中使用内联输出变量进行单行调用. (8认同)
  • 从 Core 2.0 开始,有一个 GetValueOrDefault 扩展方法可以执行相同的操作。(实际上有2种方法,一种带有defaultValue参数,一种没有)https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.collectionextensions.getvalueordefault (5认同)
  • Mladen的意思是`TryGetValue(key,out TV value)` (2认同)

bmm*_*m6o 31

您无法使用扩展方法获得所需的语法,并且正如其他人所说,重写方法/运算符以更改其行为通常不是一个好主意.我认为你能做的最好就是缩短你使用的名字.

那就是你需要保持IDictionary界面.如果您没有与任何需要IDictionary的代码连接,那么您可以自由定义自己的界面并让[]运算符以不同的方式工作不是问题.

无论你最终调用该函数,你都希望像这样实现它:

public static U Get<T, U>(this Dictionary<T, U> dict, T key)
    where U : class
{
    U val;
    dict.TryGetValue(key, out val);
    return val;
}
Run Code Online (Sandbox Code Playgroud)

它只执行一次查找,而实现则只执行2次查找.


hor*_*rgh 22

最后,我想出了一个使用从字典类派生并使用显式接口实现的变体:

public interface INullValueDictionary<T, U>
    where U : class
{
    U this[T key] { get; }
}

public class NullValueDictionary<T, U> : Dictionary<T, U>, INullValueDictionary<T, U>
    where U : class
{
    U INullValueDictionary<T, U>.this[T key]
    {
        get
        {
            U val;
            dict.TryGet(key, out val);
            return val;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

因此它以下列方式公开了我需要的功能:

//create some dictionary
NullValueDictionary<int, string> dict = new NullValueDictionary<int, string>
{
    {1,"one"}
};
//have a reference to the interface
INullValueDictionary<int, string> idict = dict;

try
{
    //this throws an exception, as the base class implementation is utilized
    Console.WriteLine(dict[2] ?? "null");
}
catch { }
//this prints null, as the explicit interface implementation 
//in the derived class is used
Console.WriteLine(idict[2] ?? "null");
Run Code Online (Sandbox Code Playgroud)


Tim*_*ang 10

添加DictionaryExtension类

public static class DictionaryExtension
{
    public static TValue GetValueOrDefault<TKey, TValue>
        (   this IDictionary<TKey, TValue> dictionary,TKey key)
    {
        TValue value;
        return dictionary.TryGetValue(key, out value) ? value : default(TValue);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果在字典中找不到键,它可以返回默认值.

如果这是引用类型,则Default为null.

_dic.GetValueOrDefault();
Run Code Online (Sandbox Code Playgroud)

  • 该扩展现在存在于框架中。 (7认同)

Rei*_*l-- 7

值得指出的是HybridDictionary默认情况下会这样做。您失去了泛型类型,但获得了 null-if-not-found 功能。

我认为(至少在理论上)您可以在少量值时获得性能优势。

  • 如果是 String, String dict 则 StringDictionary 不会丢失类型。 (2认同)

Mir*_*Mir 5

我前提是我不会使用它。该new关键字,而在这种情况下非常有用,可以创建错误,这些错误是真的很难找到。除此之外,您可以尝试此类。

class MyDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    public new TValue this[TKey key]
    {
        get
        {
            TValue value;
            return TryGetValue(key, out value) ? value : default(TValue);
        }
        set { base[key] = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @ arao6这将改变`Dictionary`类的合同。您将获得默认值,而不是例外。这意味着,当缺少键时,现在可以发生空引用异常,并且您无法确定何时缺少键以及键何时存在,但是包含空值。考虑一个接受“字典”的第三方方法,该方法取决于它的行为,但是您却提供了“我的字典”,但具有这种新的,相互冲突的行为。LSP表示子类在使用基类的任何地方都应该安全(正确)。这显然是由于合同变更而中断的。 (7认同)
  • 是的,这违反了** [Liskov替换原则](http://en.wikipedia.org/wiki/Liskov_substitution_principle)**,并且由此产生的错误确实令人讨厌。 (6认同)
  • @ arao6另一种查看方式是通过类型系统-认为异常是方法签名的一部分(返回类型的扩展),因此行为“在缺少键时引发异常”(C#类型系统不能当然可以表达这一点,但我们可以认为现在可以做到)。然后,这个新的`MyDictionary`索引器将具有与基类中原始索引器不同的返回类型-可能不是创建子类时想要的返回类型。 (4认同)