Introduction
Inability to pass parameters to predicates often made me consider writing search functions for custom collections. One day I figured enough is enough. In this example I will demonstrate the workaround to passing parameters to predicates, and reasoning involved. There is no download - the code is tiny. I will use MSDN’s example code for List.TrueForAll as the starting point.
Intermediate step
What if we wanted to reuse the predicate to search not only for dinosaur names that end on “saurus”, but also for those that end on “tor”? Shouldn’t it be easily done?
I only kept the minimum required code from MSDN example. To approximate a more common situation, the code is moved into a class
DinoClassification
, which hypothetically classifies dinosaurs. The predicate and the variable that it uses are made non-static and also moved into that class.
using System;
using System.Collections.Generic;
public class DinoClassification
{
private string m_Suffix;
public void Classify()
{
List<string> dinosaurs = new List<string>(new string[] {
"Compsognathus", "Amargasaurus", "Oviraptor", "Velociraptor",
"Deinonychus", "Dilophosaurus", "Gallimimus", "Triceratops"});
m_Suffix = "saurus";
Console.WriteLine("\nFind(EndsWith {0}): {1}", m_Suffix,
dinosaurs.Find(EndsWith));
m_Suffix = "tor";
Console.WriteLine("\nFind(EndsWith {0}): {1}", m_Suffix,
dinosaurs.Find(EndsWith));
}
private bool EndsWith(String s)
{
if ((s.Length >= m_Suffix.Length) &&
(s.Substring(s.Length - m_Suffix.Length).ToLower() ==
m_Suffix.ToLower()))
{
return true;
}
else
{
return false;
}
}
}
public class Example
{
public static void Main()
{
DinoClassification dcl = new DinoClassification();
dcl.Classify();
}
}
This works in a single-threaded environment. In multi-threaded application we could have m_Suffix
written to from different threads, which would cause havoc. To make this code thread-safe one would need to have the search inside a critical section. This is out of question. Having iteration over a collection of arbitrary size in a critical section is not an option.
There’s another fact that I personally dislike about this implementation. DinoClassification
is a dinosaur classification class. It has nothing to do with matching strings that make up a dinosaur name. Member variable m_Suffix
and predicate EndsWith
do not belong in DinoClassification
class.
So, what I really want is:
- Maintain class' thread safety with no effort on my part.
- Move the predicate out of my class.
- Keep it all as simple as possible.
Solution
Why not do just that? Wrap a variable and a predicate together and tuck away into a separate class.
using System;
using System.Collections.Generic;
public class EndsWith
{
private string m_Suffix;
public EndsWith(string Suffix)
{
m_Suffix = Suffix;
}
public string Suffix
{
get { return m_Suffix; }
set { m_Suffix = value; }
}
public Predicate<string> Match
{
get { return IsMatch; }
}
private bool IsMatch(string s)
{
if ((s.Length >= m_Suffix.Length) &&
(s.Substring(s.Length - m_Suffix.Length).ToLower()
== m_Suffix.ToLower()))
{
return true;
}
else
{
return false;
}
}
}
public class DinoClassification
{
public void Classify()
{
List<string> dinosaurs = new List<string>(new string[] {
"Compsognathus", "Amargasaurus", "Oviraptor", "Velociraptor",
"Deinonychus", "Dilophosaurus", "Gallimimus", "Triceratops"});
Console.WriteLine("\nFind(EndsWith): {0}",
dinosaurs.Find(new EndsWith("saurus").Match));
EndsWith predicate = new EndsWith("tor");
Console.WriteLine("\nFind(EndsWith): {0}",
dinosaurs.Find(predicate.Match));
predicate.Suffix = "hus";
Console.WriteLine("\nFind(EndsWith): {0}",
dinosaurs.Find(predicate.Match));
}
}
public class Example
{
public static void Main()
{
DinoClassification dcl = new DinoClassification();
dcl.Classify();
}
}
EndsWith
is a class that wraps the predicate and the variable. The variable can be set either through constructor parameter or Suffix
property. Class has two properties. Property Suffix
allows setting a new value for predicate to match. The Match
property is of type predicate<string>
. It returns the “parameterized” predicate used in Find()
, Exists()
, etc.
What was achieved in this example:
DinoClassification
is thread-safe as long as there is no member variable of type EndsWith
.
DinoClassification
is not cluttered with code pieces that belong to utility layer.
- Predicate parameters are passed to constructor and may later be modified through properties of the wrapper class.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.