是否有由索引器返回的协变类型参数的内置泛型接口

本文关键字:内置 泛型接口 类型参数 返回 索引 是否 | 更新日期: 2023-09-27 17:50:44

在这个线程中

如何获得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;
        }
    }
}

因为在实际的应用程序中我有一个字典列表,所以我需要一种从集合中作为接口访问字典的方法。我使用简单的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>实现的。

注意我已经在List中添加了一个NullValueDictionary<string, string>的实例,这样您就不会在elist[index]行中获得ArgumentOutOfRangeException

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有一些对整体类型兼容性的优化,所以如果这些类型是兼容的,你就不会遍历序列。

转换实现来自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; }
}

但是再次-如果创建一个新的接口和类对你来说是ok的,你不想到处乱扔-我认为这是一个很好的解决方案,如果你考虑了约束,它给出了。

在mscorlib

中查找协方差

这可能对你不感兴趣,但我只是想找出在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