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.