foreach with options?

  • Thread starter Thread starter Steve Barnett
  • Start date Start date
S

Steve Barnett

I have a collection which I need to navigate through in several different
ways. Is there any way to structure my class such that I can have several
different iterators?

Ideally, I'd like to do something like:

// Iterate whole collection
foreach (object in collection)

// Iterate over all objects with the "open" flag set
foreach (object in collection.opennodes)

// Iterate over all blue objects
foreach (object in collection.bluenodes)

Is there some way I can do this? The underlying collection is a generic
dictionary, so I understand that I need to handle the selection logic. My
brain is telling me that I need to set flags that control the foreach loop
before I enter it, but that seems to archaic. I will know in advance how
many different types of iterator I will need.

Any suggestions?
Thanks
Steve
 
Steve said:
I have a collection which I need to navigate through in several different
ways. Is there any way to structure my class such that I can have several
different iterators?

Yes, give it properties returning different views:

class MyClass: ICollection<Foo> {
ICollection<Foo> AllNodes { get { return this; } }
ICollection<Foo> BlueNodes { get { return /* something else */; } }
}

Which would allow you to rewrite to:
Ideally, I'd like to do something like:

// Iterate whole collection
foreach (object in collection)

foreach( object o in myClassIntances )

or

foreach( object o in myClassIntances.AllNodes )

// Iterate over all objects with the "open" flag set
foreach (object in collection.opennodes)

foreach ( object o in myClassInstance.OpenNodes )
// Iterate over all blue objects
foreach (object in collection.bluenodes)

My general suggestion is to write a "filter-dictionary", so you can simply:

Dictionary<Foo,Bar> dict = ...;
foreach ( Foo foo in
new FilterCollection(
dict.Keys,
delegate(Foo x) { return x.isBlue(); })
...;

If you often do the same transformation, give it a name:

public static ICollection<Foo> BlueNodes(ICollection<Foo> nodes) {
return new FilterCollection(
node,
delegate(Foo x) { return x.isBlue(); });
}

and if your "masert-dictionary" collection should supply callers with
different views, as suggested at the top of this post:

public class MyDict: IDictionary<Foo, Bar> {
...
ICollection Foo BlueKeys { get { return BlueNodes(Keys); } }
}

Here as an implementation suggestion:

/// <summary>
/// Filters an ICollection to only contains the values satisfying a
predicate
/// </summary>
public class FilterCollection<T> : ICollection<T>
{
public ICollection<T> Parent;
public delegate bool FilterFunction(T t);
public FilterFunction Filter;
public FilterCollection(ICollection<T> parent, FilterFunction
filter)
{
this.Parent = parent;
this.Filter = filter;
}
public int Count
{
get
{
int count = 0;
foreach (T t in Parent)
if (Filter(t))
++count;
return count;
}
}
public bool IsReadOnly { get { return Parent.IsReadOnly; } }
public void Add(T item)
{
if (!Filter(item))
throw new ArgumentException(string.Format("Filter
rejected {0}", item));
else
Parent.Add(item);
}
public void Clear() { throw new Immutable(this); }
public bool Contains(T item) { return Parent.Contains(item) &&
Filter(item); }
public void CopyTo(T[] array, int arrayIndex)
{
foreach (T t in this)
array[arrayIndex++] = t;
}
public bool Remove(T item) { return Filter(item) &&
Parent.Remove(item); }
public IEnumerator<T> GetEnumerator()
{
foreach (T t in Parent)
if (Filter(t))
yield return t;
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

/// <summary>
/// Filters an IDictionary to only contain the entries satisfying a
predicate
/// </summary>
public class FilterDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
public IDictionary<TKey, TValue> Parent;
public delegate bool FilterFunction(TKey key, TValue value);
public FilterFunction Filter;
public FilterDictionary(IDictionary<TKey, TValue> parent,
FilterFunction filter)
{
this.Parent = parent;
this.Filter = filter;
}
public virtual TValue this[TKey key]
{
get
{
TValue val = Parent[key];
if (Filter(key, val))
return val;
else
throw new KeyNotFoundException(string.Format("Not
accepted by filter: {0}->{1}", key, val));
}
set
{
if (Filter(key, value))
Parent[key] = value;
else
throw new KeyNotFoundException(string.Format("Not
accepted by filter: {0}", key));
}
}
protected bool keyFilter(TKey key) { return Filter(key,
Parent[key]); }
public ICollection<TKey> Keys { get { return new
FilterCollection<TKey>(Parent.Keys, keyFilter); } }
public ICollection<TValue> Values { get { throw new
NotImplementedException("Pending need for implementation"); } }
public int Count { get { return Keys.Count; } }
public bool IsReadOnly { get { return Parent.IsReadOnly; } }
public bool ContainsKey(TKey key)
{
TValue val;
return TryGetValue(key, out val);
}
public void Add(TKey key, TValue value)
{
if (!Filter(key, value))
throw new ArgumentException(string.Format("Filter
rejected {0}=>{1}", key, value));
Parent.Add(key, value);
}
public bool Remove(TKey key)
{
TValue val;
return TryGetValue(key, out val) && Parent.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value) { return
Parent.TryGetValue(key, out value) && Filter(key, value); }
public void Add(TKey item) { throw new
NotSupportedException("Cannot add key-only"); }
public void Clear() { throw new
NotSupportedException(string.Format("Cannot clear {0}", GetType())); }
public bool Contains(TKey item) { return ContainsKey(item); }
public void CopyTo(TKey[] array, int arrayIndex) {
this.Keys.CopyTo(array, arrayIndex); }
public IEnumerator<TKey> GetEnumerator()
{ return Keys.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { throw new
NotImplementedException(); }
public DictionaryKeyValuePairCollection<TKey, TValue>
AsCollection(IEqualityComparer<TValue> comparer)
{ return new DictionaryKeyValuePairCollection<TKey,
TValue>(this, comparer); }
public DictionaryKeyValuePairCollection<TKey, TValue> AsCollection()
{ return new DictionaryKeyValuePairCollection<TKey, TValue>(this); }
IEnumerator<KeyValuePair<TKey, TValue>>
IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{ return AsCollection().GetEnumerator(); }
public bool Remove(KeyValuePair<TKey, TValue> kvp)
{ return AsCollection().Remove(kvp); }
public bool Contains(KeyValuePair<TKey, TValue> kvp)
{ return AsCollection().Contains(kvp); }
public void Add(KeyValuePair<TKey, TValue> kvp)
{ AsCollection().Add(kvp); }
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
{ AsCollection().CopyTo(array, index); }
}
}
 
I have a collection which I need to navigate through in several
different ways. Is there any way to structure my class such that I can
have several different iterators?

Ideally, I'd like to do something like:

// Iterate whole collection
foreach (object in collection)
// Iterate over all objects with the "open" flag set
foreach (object in collection.opennodes)
// Iterate over all blue objects
foreach (object in collection.bluenodes)

It depends really. If you got a simple way of extracting a new collection
containing only the nodes you're interested in then I suggest you go with
that. For instance, if you got a dictionary or other type of index giving
you simple access to your nodes based on some criteria value, then that might
be the best solution.

Otherwise, if you're using .NET 2.0 you can take advantage of the new enumerator
syntax and write simple methods. For instance, here is the one for the blue
nodes loop:

public class YourCollection
{
....
public IEnumerable<Node> BlueNodes()
{
foreach (Node n in this)
if (n.IsBlue)
yield return n;
}
....
}

then you can simply use it like this:

foreach (Node n in yourCol.BlueNodes())
....

this will of course internally eat through your whole collection but the
above two lines of code will only see the blue ones. You can extrapolate
the rest of your criteria from this.

if you need the same in .NET 1.x then I'm afraid you need to build a class
implementing the IEnumerable interface and internally do the same kind of
check in the MoveNext method. If you need an example of such a solution then
respond here and I'll give it a shot.
 
Helge & Lasse,
Many thanks for this. I thought it would be far more complicated than
this (and was on the verge of giving up).

Very much appreciated.
Steve
 
Back
Top