2 Replies - 9830 Views - Last Post: 13 March 2013 - 05:01 AM

#1 Momerath  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 1010
  • View blog
  • Posts: 2,444
  • Joined: 04-October 09

IEnumerable<T> and You!

Posted 12 March 2013 - 08:56 PM

I do a lot of collection coding and most of them implement IEnumerable<T>. One thing about it, if you didn't know, is that you need to track if the collection changes so you can invalidate the IEnumerator when it does. I've been using the same method that Microsoft uses, setting a 'version' integer that is incremented on an add/delete and comparing it whenever you get the next enumerated value. It's fast and easy to do, but has always struck me as the wrong way to go about it in a language that has events.

So my question to you is: Would you do it the way Microsoft does, or have an event that the IEnumerator subscribes to that is triggered off an add/delete?

Is This A Good Question/Topic? 0
  • +

Replies To: IEnumerable<T> and You!

#2 Martyr2  Icon User is online

  • Programming Theoretician
  • member icon

Reputation: 4337
  • View blog
  • Posts: 12,137
  • Joined: 18-April 07

Re: IEnumerable<T> and You!

Posted 12 March 2013 - 09:14 PM

I would probably do it the Microsoft way myself since it is probably the easiest and straight forward way of doing it. But if I know the list is going to be altered, instead of enumerating with an Enumerator I might be tempted to just go through a typical loop. Again depends on how much altering I am going to be doing.

:)
Was This Post Helpful? 0
  • +
  • -

#3 baavgai  Icon User is online

  • Dreaming Coder
  • member icon

Reputation: 5833
  • View blog
  • Posts: 12,687
  • Joined: 16-October 07

Re: IEnumerable<T> and You!

Posted 13 March 2013 - 05:01 AM

View PostMomerath, on 12 March 2013 - 10:56 PM, said:

you need to track if the collection changes so you can invalidate the IEnumerator when it does.


Hmm... not my understanding.

Quote

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.

The enumerator does not have exclusive access to the collection; therefore, enumerating through a collection is intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Default implementations of collections in the System.Collections.Generic namespace are not synchronized.
-- http://msdn.microsof...y/78dfe2yb.aspx


Undefined means undefined. It could fail any way it likes.

I've never much worried about it. However, it I was concerned, I'd take advantage of events... I feel test code coming on.

Some testing code:
interface IStuff : IEnumerable<int> {
	bool Push(int n);
	bool Pop(out int n);
}

class Program {
	static void Test(IStuff stuff) {
		for (int i = 2; i < 20; i += 2) { stuff.Push(i); }
		foreach (var i in stuff) { Debug.Write(i + " "); } Debug.WriteLine("");
		var e = stuff.GetEnumerator();
		while (e.MoveNext()) { Debug.Write(e.Current + " "); } Debug.WriteLine("");

		var e2 = stuff.GetEnumerator();
		e2.MoveNext(); Debug.WriteLine(e2.Current);
		e2.MoveNext(); Debug.WriteLine(e2.Current);
		Debug.WriteLine("Invalidate");
		int n; stuff.Pop(out n);
		Debug.WriteLine("Value After Invalidate");
		e2.MoveNext(); Debug.WriteLine(e2.Current);

		var e3 = stuff.GetEnumerator();
		Debug.WriteLine("No Movenext");
		Debug.WriteLine(e3.Current);
	}

	static void Main(string[] args) {
		// Test(new Stuff());
		Test(new Stuff2());

	}
}



First IStuff implementation, no brains:
class Stuff : IStuff {
	private int[] data;
	public Stuff(int capacity = 10) {
		data = new int[capacity];
	}
	public bool Push(int n) {
		if (IsFull) { return false; }
		this.data[Size++] = n;
		return true;
	}
	public bool Pop(out int n) {
		if (Size == 0) { n = -1; return false; }
		n = this.data[--Size];
		return true;
	}
	public int Size { get; private set; }
	public int Capacity { get { return data.Length; } }
	public bool IsFull { get { return Size == Capacity; } }

	public IEnumerator<int> GetEnumerator() {
		for (int i = 0; i < Size; i++) {
			yield return this.data[i];
		}
	}
	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}



Results:
2 4 6 8 10 12 14 16 18 
2 4 6 8 10 12 14 16 18 
2
4
Invalidate
Value After Invalidate
6
No Movenext
0




Now we want our own IEnumerable. One that we can invalidate and notify of it's invalidation. I thought it was easier to separate the two functions. First, an IEnumerable will a kill switch:
class EnumeratorWithInvalidate<T> : IEnumerator<T> {
	private IEnumerator<T> innerEnumerator;
	private T invalidValue;

	public EnumeratorWithInvalidate(IEnumerator<T> innerEnumerator, T invalidValue = default(T)) {
		this.innerEnumerator = innerEnumerator;
		this.invalidValue = invalidValue;
	}
	public bool Invalidated { get { return this.innerEnumerator == null; } }
	protected void Invalidate() { if (!Invalidated) { InvalidateProcess(); } }
	protected virtual void InvalidateProcess() { this.innerEnumerator = null; }

	public T Current { get { return Invalidated ? invalidValue : innerEnumerator.Current; } }
	public void Dispose() { Invalidate(); }
	object System.Collections.IEnumerator.Current { get { return this.Current; } }

	public bool MoveNext() { return (!Invalidated) ? this.innerEnumerator.MoveNext() : false; }
	public void Reset() { if (!Invalidated) { this.innerEnumerator.Reset(); } }
}



So, basically just a wrapper class with a flag and proper behavior when that flag gets set.

As to the notify part, I thought INotifyPropertyChanged on Size made sense.
class Stuff2 : IStuff, System.ComponentModel.INotifyPropertyChanged {
	public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
	private int[] data;
	private int size;
	public Stuff2(int capacity = 10) {
		data = new int[capacity];
		size = 0;
	}
	public bool Push(int n) {
		if (IsFull) { return false; }
		this.data[Size++] = n;
		return true;
	}
	public bool Pop(out int n) {
		if (Size == 0) { n = -1; return false; }
		n = this.data[--Size];
		return true;
	}
	public int Size {
		get { return size; }
		private set {
			if (value != size) {
				size = value;
				if (PropertyChanged != null) {
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Size"));
				}
			}
		}
	}

	public int Capacity { get { return data.Length; } }
	public bool IsFull { get { return Size == Capacity; } }

	private IEnumerator<int> GetBasicEnumerator() {
		for (int i = 0; i < Size; i++) {
			yield return this.data[i];
		}
	}

	private class Stuff2Enumerator : EnumeratorWithInvalidate<int> {
		private Stuff2 instance;
		public Stuff2Enumerator(Stuff2 instance)
			: base(instance.GetBasicEnumerator(), -1) {
			this.instance = instance;
			this.instance.PropertyChanged += instanceInvalidated;
		}

		private void instanceInvalidated(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
			Invalidate();
		}

		protected override void InvalidateProcess() {
			base.InvalidateProcess();
			this.instance.PropertyChanged -= instanceInvalidated;
		}
	}

	public IEnumerator<int> GetEnumerator() { return new Stuff2Enumerator(this); }
	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}



Results:
2 4 6 8 10 12 14 16 18 
2 4 6 8 10 12 14 16 18 
2
4
Invalidate
Value After Invalidate
-1
No Movenext
0



I could probably generalize that a little more, if I used it that often. But there you are, an event for IEnumerator invalidation.
Was This Post Helpful? 1
  • +
  • -

Page 1 of 1