Question

1st question is about way of protecting my List from changing(remove/add/clear etc...) from outside

there is my way:

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

Is it good? Or is there any better ideas, or may be patterns?

2nd: When iam testing this solution with stopwatch, i was very surprised.

List -enumeration was slower than List.ToArray() enumeration with cast time:

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);

It shows me 700 msec, and

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);

Shows me 843 msec... why is it so?

Was it helpful?

Solution 2

For read only collection you could use List<T>.AsReadOnly()

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

If necessary and in order to make more clear that we're talking about a read only collection you could define your property like this

public IReadOnlyList<int> MyCollection

OTHER TIPS

First off, if you have two questions then post two questions. I'll answer your first question.

Second, good for you for thinking about how to protect your list from mutation. A lot of people forget about this and expose their internal state to callers.

There are a number of things you can do.

1) Don't use a mutable list in the first place. Use an immutable list. The Add method of an immutable list returns a different list rather than mutating the current list. There are new immutable list classes in the BCL now. These can be very efficient in both space and time.

2) Use a mutable list and make a copy every time. You're already doing this. The problem is of course that it is slow and uses a lot of memory.

3) Return AsReadOnly as Claudio suggests. Note that this just makes a read-only façade around the list; if the list changes, the read-only façade changes too. "Read only" means just that: the user cannot write it. It does not mean that it never changes. Also, there's an enumeration issue, which I'll address in (4):

4) Make LINQ do the work. return _myCollection.Select(x=>x); and have the property be of type IEnumerable<int>. This has two downsides. First, the caller only gets forward enumeration. Second, suppose the caller is doing a foreach over the property, and then inside the loop does something which changes the list. This will cause an exception; you can't change a list while you're enumerating it. If you need to support that scenario then (1) or (2) are your best bet; in those the enumeration will be over a snapshot, not over the mutating list.

Answer to the Second Question in the Question

Iterating over an int array vs iterating over a list of integers produces very different outputs at the IL code level.

Iterating over an Integer Array

  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

Iterating over an Integer List

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

The key here is that when using a list, the CLR is performing a type each iteration ( get_Current() object type to int). This may be causing performance issues.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top