SortedList<> 及其奇怪的设置器

SortedList<> and its strange setters

我最初有一些代码,简化后看起来像这样:

var planets = new List<Planet>
{
    new Planet {Id = 1, Name = "Mercury"},
    new Planet {Id = 2, Name = "Venus"},
};

我遇到了一个场景,列表被同时填充,但读取速度不够快。因此,我将其更改为使用 SortedList。

后来我意识到我可以这样重写它

var planets = new SortedList<int, Planet>
{
    {1, new Planet {Id = 1, Name = "Mercury"}},
    {2, new Planet {Id = 2, Name = "Venus"}},
    //in my actual code, i am reading the ids from a db
};

但在我采用这种方法之前,我的代码是这样写的

var planets = new SortedList<int, Planet>
{
    Keys = {1, 2},
    Values =
    {
        new Planet {Id = 1, Name = "Mercury"},
        new Planet {Id = 2, Name = "Venus"},
    }
};

这给了我这个例外

System.NotSupportedException: This operation is not supported on SortedList
nested types because they require modifying the original SortedList.
  at System.ThrowHelper.ThrowNotSupportedException(ExceptionResource resource)
  at System.Collections.Generic.SortedList`2.KeyList.Add(TKey key)

我发现这很奇怪,因为恕我直言,我并没有像它声称的那样真正修改 "original SortedList",它在说什么 "nested types"?它是 SortedList 内部的密钥列表吗?

然后我看到 SortedList 中的 KeysValues 属性实际上没有设置器。它们是只读属性,但我没有收到编译时错误。正如我在 KeyList.Add 的堆栈跟踪中看到的那样,我可以进行设置调用。我觉得失败的唯一原因是 SortedList 中的显式检查,这对我来说似乎很奇怪!

例如 var str = new String {Length = 0}; 给了我预期的编译时错误,因为 Length 是只读的 属性,planets.Keys = null;

也是如此

有人请告诉我 - 我在这里忽略了什么简单的事实?

您编写的代码与此类似:

var planets = new SortedList<int, Planet>();
planets.Keys.Add(1);
planets.Keys.Add(2);
planets.Values.Add(new Planet { Id = 1, Name = "Mercury" });
planets.Values.Add(new Planet { Id = 2, Name = "Venus" });

SortedList 需要通过 SortedList<TKey, TValue>.Add(TKey key, TValue value) 方法同时添加值和键,这样它可以根据键对值进行排序。内部用于 KeysValuesIList<T> 的实现不支持通过 IList<T>.Add(T value) 方法独立添加相应的键或值。

您应该可以通过调用 Keys.Add(...)Values.Add(...)

来重现此错误

我对 SortedList 的最初查询现在已最小化为对数组、集合和对象初始值设定项以及编译器以不同方式解释它们的方式的关注。再次感谢@Haney 的第一个回答,引导我得出这个观点,并感谢 ILSpy 的这些见解。

下面是一些数组和集合初始化器:

int[] a = { 1, 2, 3 };
int[] b = new int[] { 1, 2, 3 };
IList<int> c = { 1, 2, 3 };
IList<int> d = new int[] { 1, 2, 3 };

它们看起来都有点相似。在这里,编译器为 a 和 b 产生完全相同的输出。对于 c,我们将得到这个编译时错误:

Can only use array initializer expressions to assign to array types. Try using a new expression instead.

这是有道理的,因为我们不应该为集合使用数组初始值设定项。但是,d 产生与 a & b 完全相同的结果。我认为那也是一个数组初始化器。显然不是。

现在考虑这个class

class MyCollectionContainer
{
    public int[] MyIntArray { get; set; }
    public IList<int> MyList { get; set; }
}

以及对其进行操作的代码

var containerA = new MyCollectionContainer { MyIntArray = { 1, 2, 3 } };
var containerB = new MyCollectionContainer { MyIntArray = new int[]{ 1, 2, 3 } };
var containerC = new MyCollectionContainer { MyList = { 1, 2, 3 } };
var containerD = new MyCollectionContainer { MyList = new int[]{ 1, 2, 3 } };

containerA 给出了这个编译时错误:

Cannot initialize object of type 'int[]' with a collection initializer

对于containerB,编译器有效地将其转换成这段代码:

MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyIntArray = new int[] {1, 2, 3};

对于 containerD,它几乎是一样的,除了它的另一个 属性 被初始化的事实:

MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyList = new int[] {1, 2, 3};

对于 containerC,编译器将其变形为:

MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyList.Add(1);
myCollectionContainer.MyList.Add(2);
myCollectionContainer.MyList.Add(3);

这导致 运行 次 NullReferenceException,因为 MyList 未初始化。

这意味着在此处初始化集合容器对象的唯一有效方法是 containerB 和 containerD。对我来说,这清楚地表明对象初始化器与数组和集合初始化器相比在编译器解释它们的方式上是不同的。