在这个帖子中
如何获取 null 而不是通过键访问 Dictionary 值时出现 KeyNotFoundException?
在我自己的答案中,我使用显式接口实现来更改基本字典索引器行为,如果字典中不存在该键,则不会抛出
KeyNotFoundException
(因为在这种情况下,我很方便获得 null
右内联) ).
这是:
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
{
if (ContainsKey(key))
return this[key];
else
return null;
}
}
}
由于在实际应用程序中我有一个字典列表,因此我需要一种方法作为接口从集合中访问字典。我使用简单的
int
索引器来访问列表中的每个元素。
var list = new List<NullValueDictionary<string, string>>();
int index = 0;
//...
list[index]["somekey"] = "somevalue";
最简单的事情就是做这样的事情:
var idict = (INullValueDictionary<string, string>)list[index];
string value = idict["somekey"];
当我决定尝试使用协方差功能来替代使用一组接口时提出的问题。因此,我需要一个具有协变类型参数的接口才能进行强制转换。我想到的第一件事是
IEnumerable<T>
,所以代码看起来像这样:
IEnumerable<INullValueDictionary<string, string>> ilist = list;
string value = ilist.ElementAt(index)["somekey"];
一点也不太好,此外
ElementAt
代替索引器更糟糕。
List<T>
的索引器在 IList<T>
中定义,并且 T
不存在协变。
我该怎么办?我决定自己写:
public interface IIndexedEnumerable<out T>
{
T this[int index] { get; }
}
public class ExtendedList<T> : List<T>, IIndexedEnumerable<T>
{
}
嗯,几行代码(我什至不需要在
ExtendedList<T>
中写任何东西),它就按照我想要的方式工作了:
var elist = new ExtendedList<NullValueDictionary<string, string>>();
IIndexedEnumerable<INullValueDictionary<string, string>> ielist = elist;
int index = 0;
//...
elist[index]["somekey"] = "somevalue";
string value = ielist[index]["somekey"];
最后一个问题:可以在不创建额外集合的情况下以某种方式实现这种协变强制转换吗?
你可以尝试使用
IReadOnlyList<T>
,它是由List<T>
实现的。
注意,我已将
NullValueDictionary<string, string>
的一个实例添加到 List
,这样您就不会在 ArgumentOutOfRangeException
行得到 elist[index]
。
IReadOnlyList<NullValueDictionary<string, string>> elist = new List<NullValueDictionary<string, string>>
{
new NullValueDictionary<string, string>()
};
IReadOnlyList<INullValueDictionary<string, string>> ielist = elist;
int index = 0;
//...
elist[index]["somekey"] = "somevalue";
string value = elist[index]["somekey"];
编辑: 我已经搜索了 .NET 4.5 之前带有索引的协变接口和集合,但没有找到。不过,我认为有比创建单独的界面更简单的解决方案 - 只是将一个集合转换为另一个集合。
List<INullValueDictionary<string, string>> ielist = elist.Cast<INullValueDictionary<string, string>>().ToList();
或者使用从数组获得的协方差
INullValueDictionary<string, string>[] ielist = elist.ToArray()
LINQ 有一些针对整个类型兼容性的优化,因此如果这些类型兼容,您将不会迭代序列。
Cast 实现取自 MONO Linq
public static IEnumerable<TResult> Cast<TResult> (this IEnumerable source)
{
var actual = source as IEnumerable<TResult>;
if (actual != null)
return actual;
return CreateCastIterator<TResult> (source);
}
请注意,我已更改
INullValueDictionary<T, U>
接口以在属性中包含 set
,以便 ielist[index]["somekey"] = "somevalue";
可以工作。
public interface INullValueDictionary<T, U> where U : class
{
U this[T key] { get; set; }
}
但同样 - 如果创建一个新的接口和类对你来说没问题,并且你不想到处乱搞强制转换 - 我认为这是一个很好的解决方案,如果你考虑了约束,它给出了。
您可能对此不感兴趣,但我只是想找出 mscorlib 程序集中哪些类型是协变的。通过运行下一个脚本,我只收到 17 种类型是协变的,其中 9 种是
Func
。我省略了 IsCovariant
实现,因为即使没有它,这个答案也太长了
typeof(int).Assembly.GetTypes()
.Where(type => type.IsGenericType)
.Where(type=>type.GetGenericArguments().Any(IsCovariant))
.Select(type => type.Name)
.Dump();
//Converter`2
//IEnumerator`1
//IEnumerable`1
//IReadOnlyCollection`1
//IReadOnlyList`1
//IObservable`1
//Indexer_Get_Delegate`1
//GetEnumerator_Delegate`1
asdfadsfadsadsgasdgasdgasdgasdfasdfasddgads