Stacking the Predicate/Converter functions on top of the generic ForEach method...
I figured it would take me a few hours to pump out a decent ForEach sample, but I'm not thinking so. At first I figured ForEach would provide a bunch of enumerator protection semantics, but it doesn't. It just does a for loop over the internal array (and not because of the C# foreach optimization either, since this guy only has a single local int and they aren't making a copied reference of the _items array).
Well hell, I can do that. I've added the ForEach that now takes a list to mutate, a sub list predicate, a sub list converter (as in the last article), but adds the concept of a BreakAction. Why a BreakAction? Well, because the System.Action<T> delegate doesn't support a boolean return value. How do you break out of a foreach statement? Well, you use a break; call. You can't do that with the System.Action<T> delgate, so I created a BreakAction<T> delegate that will work. Note that BreakAction<T> now also uses SubType whereas the ForEach on the List<T> would make use of ListType.
Since delegates make heavy use of type parameters when declaring EVERYTHING, it wouldn't save you much code to put these helper methods on the List<T> now that I think about it. ForEach by default could do what it does now, but as soon as you start adding the sub-list parsing, the calling code would look just as ugly as the runnable code sample here:
using System;
using System.Collections;
using System.Collections.Generic;
public class Organism {}
public class Plant : Organism {}
public class Animal : Organism {}
public class Carnivore : Animal {}
public class Herbivore : Animal {}
public interface ISeaPlant {}
public interface ILandPlant {}
public class SeaPlant : Plant, ISeaPlant {}
public class LandPlant : Plant, ILandPlant {}
public delegate bool BreakAction<DataType>(DataType dataToProcess);
public class GenericForEachEnhancement {
private static void Main(string[] args) {
List<Organism> organisms = new List<Organism>(30);
for(int i = 0; i < 10; i++) {
organisms.Add(new Plant());
organisms.Add(new SeaPlant());
organisms.Add(new LandPlant());
organisms.Add(new Carnivore());
organisms.Add(new Herbivore());
}
bool enumerationBreak = ForEach<Plant, Organism>(
organisms,
new Predicate<Organism>(PlantsOnlyPredicate),
new Converter<Organism, Plant>(PlantsConverter),
new BreakAction<Plant>(MyBreakAction));
Console.WriteLine();
Console.WriteLine("Enumeration Break?: {0}", enumerationBreak);
}
private static bool PlantsOnlyPredicate(Organism underlying) {
if ( underlying is Plant ) { return true; } else { return false; }
}
private static Plant PlantsConverter(Organism input) {
return input as Plant;
}
private static Random rand = new Random();
private static bool MyBreakAction(Plant plantToProcess) {
Console.WriteLine("Processing Plant");
return (rand.Next(0, 30) == 0);
}
private static bool ForEach<SubType, ListType>(
List<ListType> listToMutate,
Predicate<ListType> subListPredicate,
Converter<ListType, SubType> subListConverter,
BreakAction<SubType> actionOrBreak) {
// I would be using FindAllMutate probably, but I'll just rewrite the code here
foreach(ListType listItem in listToMutate) {
if ( subListPredicate(listItem) ) {
if ( actionOrBreak(subListConverter(listItem)) ) {
return true; // The enumeration was broken
}
}
}
// The enumeration full completed
return false;
}
}
The last step in the process (making this work for ControlCollection) will definitely have to come later. Time for bed.