如何获得 null 而不是 KeyNotFoundException 通过键访问 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;
}

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

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

我想说,最好有一些接近基本语法的东西: 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;
}
}
}

但是,要获得基本行为上的细微变化,似乎有点费力。

另一个解决方案是在当前上下文中定义一个 Func,如下所示:

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

所以它可以像 GetDictValueByKeyOrNull("key1")一样被利用。

你能给我更多的建议或帮助我选择一个更好的吗?

89574 次浏览

I premise by saying that I would not use this. The new keyword, while useful in this case, can create bugs which are really hard to find. Other than that, you can try this class.

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

In the end I came up with a variant using a deriving from dictionary class with explicit interface implementation:

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;
this.TryGetValue(key, out val);
return val;
}
}
}

So it exposes the functionality I need the following way:

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

You can't get the syntax you want with an extension method, and as others have said overriding a method/operator to change its behavior is generally not a great idea. I think the best you can do is shorten the name you use.

That's if you need to keep to the IDictionary interface. If you aren't interfacing with any code that expects an IDictionary, then you are free to define your own interface and having the [] operator work differently isn't a problem.

Whatever you end up calling the function, you'll want to implement it like this:

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

It just does one lookup, compared with 2 for your implementations.

Worth pointing out that the HybridDictionary does this by default. You lose the generic typeing, but get the null-if-not-found functionality.

And (at least theoretically) you get performance benefits at low numbers of values, I think.

Here is my solution from my personal library, implemented as an extension method. I am only posting it because it is implemented from the dictionary interface and allows an optional default value to be passed in.

Implementation

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

Usage

 MyDictionary.GetValue("key1");
MyDictionary.GetValue("key2", -1);
MyDictionary.GetValue("key3")?.SomeMethod();

I've done this before and it worked pretty well to just inherit the regular Dictionary class and just hide the indexer. It's really clean to do it this way so you automatically acquire all the reliability and familiarity of the Regular Dictionary class.

public class NullSafeDict<TKey, TValue> : Dictionary<TKey, TValue> where TValue : class
{
public new TValue this[TKey key]
{
get
{
if (!ContainsKey(key))
return null;
else
return base[key];
}
set
{
if (!ContainsKey(key))
Add(key, value);
else
base[key] = value;
}
}
}

Add a DictionaryExtension class

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

And it can return Default value if not found key in dictionary.

The Default is null if this is reference type .

_dic.GetValueOrDefault();