foreach & arraylist

  • Thread starter Thread starter Lloyd Dupont
  • Start date Start date
L

Lloyd Dupont

Does any of you have the slightest ideas of why you can't modify an array
list while in a foreach of its element.

I wrote my own collection and I'm trying to add the same behavior but I
don't know where to start.

I though to internal events but dismiss the idea because it prevents garbage
collection of the enumerator.
any other ideas ?
 
Lloyd Dupont said:
Does any of you have the slightest ideas of why you can't modify an array
list while in a foreach of its element.
Because that is the rules when it comes to enumerators, it makes enumerator
implementation alot easier. Allowing changes requires a more sophisticated,
thread-safe enumerator. The probability of a bug in that case would be very
large in deed.
I wrote my own collection and I'm trying to add the same behavior but I
don't know where to start.

I though to internal events but dismiss the idea because it prevents garbage
collection of the enumerator.
any other ideas ?
Well, simple method, stick a private field like int version; in your class
and increment it on every change, following your proper locking rules and
methodologies of course(you may need to use
System.Threading.Interlocked.Increment in this case, assuming multiple
threads, enumerators, and processors). Then on every command to the
enumerator check that value, if it changed since the enumerator was created,
your enumerator is invalid and should throw an exception as specified in the
IEnumerable documentation.
 
Lloyd Dupont said:
Does any of you have the slightest ideas of why you can't modify an array
list while in a foreach of its element.

I wrote my own collection and I'm trying to add the same behavior but I
don't know where to start.

I though to internal events but dismiss the idea because it prevents garbage
collection of the enumerator.
any other ideas ?
I forgot to mention, you could probably use internal events. foreach will
call IDisposable.Dispose if the enumerator implements IDisposable, so you
could use that call to clean up, its not as clean but it would work.
 
The usage of "in" keyword doesnt let you modify the content of the
arraylist.

As far as valuetype such as int,bool are concerned, they might be
modifiable.

But when objects are added to the arraylist, the "in" keyword in forach,
returns a read-only reference, which you cant make it point to other
reference

One cant change the behaviour of "in", since its a keyword & not an operator


for eg
foreach (person p in alist)
p = new person("my name"); // this cant be done, because p is
read-only here (as the compiler says)

but you can do the following

foreach (person p in alist)
p.name = "new name";

If you want to store a new reference in alist
for(int i =0; i < alist.Count; i++)
alist = new person("new name") // now this can be done, since it is
using subscript operator, which returns an object instance

I hope I answered, what you wanted. If anything is unclear, do write back on
the group
I am no expert though, but my understanding tells me this way to achieve it

HTH
Kalpesh
 
I like this int version inside a list, but if my list is gong to be used in
multithreaded environment, ain't all these lock() {} going to be a
performance hit ?
 
Yes a lot of spurious locks would slow you down, but this is the perfect
place to use System.Threading.Interlocked.Increment(ref int).
 
Lloyd Dupont said:
I like this int version inside a list, but if my list is gong to be used in
multithreaded environment, ain't all these lock() {} going to be a
performance hit ?
Yes. I however advised using the proper locking because I didn't know what
you were doing precisely. If your class requires locking in other
situations(it needs to be thread safe, for example) then use locks, if not,
Interlocked.Increment(ref int) is quite sufficent for simply incrementing
the version field.
 
no worry I had an idea last night.
I lock in upper level user code, and the usage is preasumably safe.

on another though this Interlocked class, looks great, it works across
thread, but does it work across process ?
I was thinking to use a shared memory channel to share data but I've heard
that mutex was a performance hit and I had better design my own custom user
level "mutex"
 
If your going to have multiple threads against a queue (for example) at the
same time, then you need to lock to keep things straight. Here is an
example of using multiple readers against a Queue. I derived a class from
Queue to change the end of queue behavior to return null instead of
Exception. Also, in the new Iterator, we treat the queue as a queue and not
as an array with a fixed size. These two things combined, allow us to have
one thread add items and multiple threads removing items (if order of
objects in not important) or any combination. Notice each thread can use
"foreach" and not throw a version exception, which is what we want here.
HTH
--William Stacey, MVP

//Put in a button event or main, etc.
Queue mysyncQueue = Queue.Synchronized(new MyQueue()); //Get a Sync'd
version of my derived Queue class.
for (int i = 1; i <= 30; i++) //Put some data in it.
{
mysyncQueue.Enqueue("Item"+i);
}
StringBuilder sbResults = new StringBuilder();
ThreadWorkerClass threadWorker1 = new ThreadWorkerClass("Thread1",
sbResults, mysyncQueue);
ThreadWorkerClass threadWorker2 = new ThreadWorkerClass("Thread2",
sbResults, mysyncQueue);
Thread thread1 = new Thread(new ThreadStart(threadWorker1.Run));
Thread thread2 = new Thread(new ThreadStart(threadWorker2.Run));
thread1.IsBackground = true;
thread2.IsBackground = true;
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine("Two threads read the following items from the shared
queue.");
Console.WriteLine(sbResults);
Console.WriteLine("Queue up some more items and print.");
mysyncQueue.Enqueue("MoreItem1");
mysyncQueue.Enqueue("MoreItem2");
foreach(string s in mysyncQueue)
{
Console.WriteLine(s);
}
//End above.

//Create a worker class that reads from shared queue.
public class ThreadWorkerClass
{
private StringBuilder sbResults;
private Queue sharedQueue;
private string threadName;
public ThreadWorkerClass(string threadName, StringBuilder sbResults, Queue
sharedQueue)
{
this.threadName = threadName;
this.sbResults = sbResults;
this.sharedQueue = sharedQueue;
}
public void Run()
{
int count = 1;
foreach(string s in sharedQueue)
{
sbResults.Append(threadName + " Dequeued: " + s + "\r\n");
if ( threadName == "Thread1" )
{
//Give thread1 more time, but only allow it to read 10 items.
if ( ++count >= 10 )
break;
Thread.Sleep(5);
}
else
Thread.Sleep(10); //Let thread2 sleep for longer to mix-up the reads a
bit.
}
sbResults.Append(threadName + " ended getting data from the queue.\r\n");
}
} //End ThreadWorkerClass

//Derive a Queue and override the Dequeue behavior.
//Also setup a enumerator that will allow threads to add to and remove items
from
//queue while we iterate over it.
public class MyQueue : Queue
{
public MyQueue() : base()
{
}
public MyQueue(ICollection col) : base(col)
{
}
public MyQueue(int capacity) : base(capacity)
{
}
public MyQueue(int capacity, float growFactor) : base(capacity, growFactor)
{
}
public override IEnumerator GetEnumerator()
{
return new MyQueueEnumerator(this);
}
public override object Dequeue()
{
object obj;
try
{
obj = base.Dequeue();
}
catch(InvalidOperationException)
{
return null; //If empty, change behavior to return null instead of
exception.
}
catch(Exception e)
{
throw e;
}
return obj;
}
private class MyQueueEnumerator : IEnumerator, ICloneable
{
private Queue queue;
private object currentElement;

internal MyQueueEnumerator(Queue queue) : base()
{
this.queue = queue;
if ( this.queue == null )
throw new ArgumentNullException("queue", "queue argument is null.");
currentElement = this.queue; //flag to state enum is not started yet.
}
public virtual bool MoveNext()
{
if ( ((currentElement = queue.Dequeue()) == null) && (queue.Count == 0) )
//Is queue empty?
{
this.currentElement = this.queue;
return false;
}
return true;
}
public virtual void Reset()
{
this.currentElement = this.queue; //reset enum flag to closed state.
}
public virtual object Current
{
get
{
if (currentElement == this.queue) //enum reset or started with no
MoveNext?
{
throw new InvalidOperationException("InvalidOperation_EnumNotStarted");
}
return currentElement;
}
}
public object Clone()
{
return base.MemberwiseClone();
}
} //End QueueEnumerator
} //End MyQueue
 
I don't understand what you said.
would you ind tell it otherwise ?

Kalpesh Shah said:
The usage of "in" keyword doesnt let you modify the content of the
arraylist.

As far as valuetype such as int,bool are concerned, they might be
modifiable.

But when objects are added to the arraylist, the "in" keyword in forach,
returns a read-only reference, which you cant make it point to other
reference

One cant change the behaviour of "in", since its a keyword & not an operator


for eg
foreach (person p in alist)
p = new person("my name"); // this cant be done, because p is
read-only here (as the compiler says)

but you can do the following

foreach (person p in alist)
p.name = "new name";

If you want to store a new reference in alist
for(int i =0; i < alist.Count; i++)
alist = new person("new name") // now this can be done, since it is
using subscript operator, which returns an object instance

I hope I answered, what you wanted. If anything is unclear, do write back on
the group
I am no expert though, but my understanding tells me this way to achieve it

HTH
Kalpesh


Lloyd Dupont said:
Does any of you have the slightest ideas of why you can't modify an array
list while in a foreach of its element.

I wrote my own collection and I'm trying to add the same behavior but I
don't know where to start.

I though to internal events but dismiss the idea because it prevents garbage
collection of the enumerator.
any other ideas ?
 
Back
Top