The generic List is a pretty powerful class. But finding one or more items is not that easy to grasp at first sight. The Find and the FindAll method take a so called Predicate as a parameter to test the individual items in a List. The documentation on this on MSDN is not very obvious. In a rather long example you will not get any further than finding items on hard coded criteria. In the real world you usually want to find items on dynamic criteria. There is some literature on the web on this but the solutions vary. Hereby a roundup.
In a small example I will also work, just like MSDN, with a list of dinosaurus names
private static List<string> buildAnimals()
{
List<string> animals = new List<string>();
animals.Add("Diplodocus");
animals.Add("Tyranosaurus");
animals.Add("Protoceratops");
animals.Add("Deinonichus");
animals.Add("Stegosaurus");
return animals;
}
In the example a selection of these names will populate a listbox. Pure presentation code.
The FindAll method takes a Predicate as a parameter. A predicate is a delegate, a definition of a method signature. The predicate method has one parameter, of the same type as the members of the list and returns a Boolean. Returning true signifies the list member does satisfy the selection criteria, returning false is a no match. When searching the list the Find(All) method will pass each list member to the method.
This is the predicate used all over the MSDN example
private static Boolean findAnimalMSDN(string name)
{
return name.EndsWith("saurus");
}
Now to parameterize this search we have to get the search criteria into this method. It cannot be passed as a method parameter, an extra member is needed.
private static string findAnimal;
private static Boolean findAnimal1(string name)
{
return name.EndsWith(findAnimal);
}
private void button1_Click(object sender, EventArgs e)
{
List<string> animals = buildAnimals();
listBox1.Items.Clear();
findAnimal = textBox1.Text;
List<string> matchinganimals = animals.FindAll(findAnimal1);
foreach (string s in matchinganimals)
listBox1.Items.Add(s);
}
Just like all MSDN example code the predicate is here a static member. To get the non static textbox value into the predicate requires a static helper string.
But the predicate does not have to be static. An instance method can read the contents of the textbox itself.
private Boolean findAnimal2(string name)
{
return name.EndsWith(textBox1.Text);
}
private void button2_Click(object sender, EventArgs e)
{
List<string> animals = buildAnimals();
listBox1.Items.Clear();
List<string> matchinganimals = animals.FindAll(findAnimal2);
foreach (string s in matchinganimals)
listBox1.Items.Add(s);
}
But still this code is pretty smelly, I don't like it at all that there are two methods needed to get just one thing done. There is an article on CodeProject by Alex Perepletov where he discusses some of these issues and creates a class to wrap everything up. But I still think that is an overkill. After all I don't want to have to write a complete class to get something very simple done.
Using anonymous delegates in C# you can combine both methods in one. Instead of writing the predicate and using it as a parameter to the FindAll method you just pass the complete method itself as a parameter.
private void button4_Click(object sender, EventArgs e)
{
List<string> animals = buildAnimals();
listBox1.Items.Clear();
List<string> matchinganimals = animals.FindAll(
delegate(string name) { return name.EndsWith(textBox1.Text); }
);
foreach (string s in matchinganimals)
listBox1.Items.Add(s);
}
The parameter is the complete method, the method signature is a delegate.
The method does not need a name, it is anonymous. In a short but clear reference on thinksharp it is named an inline delegate. The official name is anonymous delegate. The code is inline, note how it can read (and set) it's surrounding scope. Here it can read the contents of the textbox. An anonymous delegate can also read from the scope it is defined in. A variation:
private void button3_Click(object sender, EventArgs e)
{
List<string> animals = buildAnimals();
listBox1.Items.Clear();
string privateAnimal = textBox1.Text;
List<string> matchinganimals = animals.FindAll(
delegate(string name) { return name.EndsWith(privateAnimal); }
);
foreach (string s in matchinganimals)
listBox1.Items.Add(s);
}
Here it works with the local privateAnimal string.
In the example I have worked with a list of strings. But working with complete methods to test list members there is no limit on the complexity of the comparison.
Well, that's about it. Then there is the obvious question: Is this code available in VB ? It's pretty funny what the translator comes up with. But no there are no anonymous delegates in VB.