【问题标题】:SortedList<> and its strange settersSortedList<> 及其奇怪的设置器
【发布时间】:2015-01-20 01:15:45
【问题描述】:

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

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)

我觉得这很奇怪,因为恕我直言,我并没有真正修改它声称的“原始 SortedList”,它在谈论什么“嵌套类型”?它是SortedList 内部的键列表吗?

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

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

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

【问题讨论】:

    标签: c# .net setter sortedlist


    【解决方案1】:

    您编写的代码与此类似:

    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&lt;TKey, TValue&gt;.Add(TKey key, TValue value) 方法同时添加值和键,以便它可以按键对值进行排序。内部用于KeysValuesIList&lt;T&gt; 的实现不支持通过IList&lt;T&gt;.Add(T value) 方法独立添加相应的键或值。

    您应该能够通过调用Keys.Add(...)Values.Add(...) 来重现此错误

    【讨论】:

    • 谢谢@Haney。我觉得让我失望的部分是集合初始化器的工作方式。编译器对Keys = {1}Keys = new[]{1} 的处理方式不同,但对于数组初始值设定项而言并非如此。我猜如果有new[],我会得到我正在寻找的编译时错误。
    • @sudheeshix 是的,我同意他们有时只是为您“解释您的意思”,这有点令人困惑。
    【解决方案2】:

    我对@9​​87654322@ 的初始查询现在已最小化为对数组、集合和对象初始化程序以及编译器以不同方式解释它们的方式的关注。再次感谢 @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,我们会得到这个编译时错误:

    只能使用数组初始化表达式来分配数组类型。 请尝试使用新的表达式。

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

    现在考虑这个类

    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 给出了这个编译时错误:

    无法使用集合初始化器初始化“int[]”类型的对象

    对于容器B,编译器有效地将其转换为以下代码:

    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。对我来说,这清楚地表明,与数组和集合初始化程序相比,对象初始化程序在编译器解释它们的方式上是不同的。

    【讨论】:

      猜你喜欢
      • 2013-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-30
      • 1970-01-01
      • 1970-01-01
      • 2021-06-16
      • 2012-10-16
      相关资源
      最近更新 更多