0

第一个问题是关于保护我的列表免受外部更改(删除/添加/清除等)的方式

有我的方式:

class Foo 
{
    public int[] MyCollection
    {
        get{ return (_myCollection==null)?null:_myCollection.ToArray();
    }
    protected List<int> _myCollection;
}

好吗?或者有没有更好的想法,或者可能是模式?

第二:当我用秒表测试这个解决方案时,我很惊讶。

List -enumeration 比 List.ToArray() 枚举慢,有施放时间:

List<int> myList = new List<int>();
for (int j = 0; j < 10000; j++)
{
    myList.Add(j);
}
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 10000; i++)
{
    //casting every iteration:
    var ROC = myList.ToArray();
    int count = 0;
    foreach (var a in ROC)
    {
        count += a;
    }
}
sw.Stop();
Console.WriteLine(sw.Elapsed);

它显示了 700 毫秒,并且

List<int> myList = new List<int>();
for (int j = 0; j < 10000; j++)
{
    myList.Add(j);
}
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 10000; i++)
{
    int count = 0;
    //No casting at all
    foreach (var a in myList)
    {
        count += a;
    }
}
sw.Stop();
Console.WriteLine(sw.Elapsed);

显示 843 毫秒……为什么会这样?

4

3 回答 3

6

首先,如果您有两个问题,请发布两个问题。我会回答你的第一个问题。

其次,有利于您考虑如何保护您的列表免受突变。很多人忘记了这一点,并将他们的内部状态暴露给调用者。

你可以做很多事情。

1)首先不要使用可变列表。使用不可变列表。Add不可变列表的方法返回一个不同的列表,而不是改变当前列表。现在 BCL 中有新的不可变列表类。这些在空间和时间上都非常有效。

2)使用可变列表并每次制作副本。你已经在这样做了。问题当然是它很慢并且使用大量内存。

3)AsReadOnly按照克劳迪奥的建议返回。请注意,这只是在列表周围创建了一个只读外观;如果列表发生变化,只读外观也会发生变化。“只读”意味着:用户不能写它。这并不意味着它永远不会改变。此外,还有一个枚举问题,我将在 (4) 中解决:

4) 让 LINQ 完成这项工作。return _myCollection.Select(x=>x);并具有类型的属性IEnumerable<int>。这有两个缺点。首先,调用者只获得前向枚举。其次,假设调用者正在foreach对属性执行操作,然后在循环内部执行更改列表的操作。这将导致异常;枚举时不能更改列表。如果您需要支持这种情况,那么 (1) 或 (2) 是您最好的选择;在那些中,枚举将在快照上,而不是在变异列表上。

于 2013-06-14T15:43:15.497 回答
4

对于只读集合,您可以使用List<T>.AsReadOnly()

public IList<int> MyCollection
{
   get{ return _myCollection==null  ? null : _myCollection.AsReadOnly();
}

如有必要并且为了更清楚地说明我们正在谈论只读集合,您可以像这样定义您的属性

public IReadOnlyList<int> MyCollection
于 2013-06-14T15:12:33.717 回答
2

回答问题中的第二个问题

迭代 int 数组与迭代整数列表在 IL 代码级别产生非常不同的输出。

迭代整数数组

  IL_0015:  ldloc.3
  IL_0016:  ldloc.s    CS$7$0001
  IL_0018:  ldelem.i4
  IL_0019:  stloc.2
  IL_001a:  ldloc.1
  IL_001b:  ldloc.2
  IL_001c:  add
  IL_001d:  stloc.1
  IL_001e:  ldloc.s    CS$7$0001
  IL_0020:  ldc.i4.1
  IL_0021:  add
  IL_0022:  stloc.s    CS$7$0001
  IL_0024:  ldloc.s    CS$7$0001
  IL_0026:  ldloc.3
  IL_0027:  ldlen
  IL_0028:  conv.i4
  IL_0029:  blt.s      IL_0015

迭代整数列表

IL_0010:  ldloca.s   CS$5$0000
IL_0012:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0017:  stloc.1
IL_0018:  ldloc.0
IL_0019:  ldloc.1
IL_001a:  add
IL_001b:  stloc.0
IL_001c:  ldloca.s   CS$5$0000
IL_001e:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0023:  brtrue.s   IL_0010

这里的关键是,当使用列表时,CLR 每次迭代都会执行一个类型(get_Current()object type to int)。这可能会导致性能问题。

于 2013-06-14T16:04:26.000 回答