hor*_*rgh 7 c# generics indexer interface covariance
在这个帖子中
如何获取null而不是按键访问字典值的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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
因为在一个真实的应用程序中我有一个字典列表,我需要一种方法来从集合中访问字典作为接口.我使用简单的int索引器来访问列表的每个元素.
var list = new List<NullValueDictionary<string, string>>();
int index = 0;
//...
list[index]["somekey"] = "somevalue";
Run Code Online (Sandbox Code Playgroud)
最简单的事情是做这样的事情:
var idict = (INullValueDictionary<string, string>)list[index];
string value = idict["somekey"];
Run Code Online (Sandbox Code Playgroud)
当我决定尝试使用协方差特征来代替使用接口集合时提出的问题.所以我需要一个带有协变类型参数的接口才能使转换工作.我想到的第一件事是IEnumerable<T>,所以代码看起来像这样:
IEnumerable<INullValueDictionary<string, string>> ilist = list;
string value = ilist.ElementAt(index)["somekey"];
Run Code Online (Sandbox Code Playgroud)
不是那么好,除了ElementAt索引器之外更糟糕.索引器的List<T>定义是IList<T>,并且T没有协变.
我该怎么办?我决定写自己的:
public interface IIndexedEnumerable<out T>
{
T this[int index] { get; }
}
public class ExtendedList<T> : List<T>, IIndexedEnumerable<T>
{
}
Run Code Online (Sandbox Code Playgroud)
好吧,几行代码(我甚至不需要写任何东西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"];
Run Code Online (Sandbox Code Playgroud)
最后一个问题是:这个协变演员能否以某种方式实现,而无需创建额外的收藏?
您可以尝试使用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"];
Run Code Online (Sandbox Code Playgroud)
编辑:我已经在.NET 4.5之前搜索了带有索引的协变接口和集合,但没有找到.我认为还有一个更简单的解决方案,而不是创建单独的界面 - 只是为了将一个集合转换为另一个集合.
List<INullValueDictionary<string, string>> ielist = elist.Cast<INullValueDictionary<string, string>>().ToList();
Run Code Online (Sandbox Code Playgroud)
或者使用从数组中获得的协方差
INullValueDictionary<string, string>[] ielist = elist.ToArray()
Run Code Online (Sandbox Code Playgroud)
LINQ有一些优化可以处理整个类型的兼容性,因此如果这些类型兼容,您将不会迭代序列.
演员实施取自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);
}
Run Code Online (Sandbox Code Playgroud)
请注意,我已将更改的INullValueDictionary<T, U>接口更改为包含set在属性中以便ielist[index]["somekey"] = "somevalue";可以使用.
public interface INullValueDictionary<T, U> where U : class
{
U this[T key] { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
但是再次 - 如果创建一个新的接口和类对你来说没问题而且你不想在任何地方乱扔乱码 - 我认为这是一个很好的解决方案,如果你考虑过限制,它会给出.
这对您来说可能不会感兴趣,但我只是想知道mscorlib程序集中哪些类型是协变的.通过运行下一个脚本我收到的只有17种类型是协变的,其中9种是Funcs.我省略了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
Run Code Online (Sandbox Code Playgroud)