Disclaimer
I just want to start by saying I don't recommend doing this and you should go with unholysampler's answer (or your current solution; You're not saving much code there).
I'm posting this possibility to show you what could be and why you shouldn't do it. I was once in a similar place to you and thought it would be great to do something like this, but it was not worth the time or effort (though it was a nice learning experience).
"Solution"
Okay, so you can technically make a function where you build the expression from a property selector, operator (enum), and value you're comparing to.
Downfall #1: Using this method, you'll see that you're just moving the switch-statement you're trying to get rid of. So if your goal is to reduce the number of lines of code this won't help.
NOTE: This was written in a unit test class.
private static Expression<Func<T, bool>> PredicateBuilder<T, TProp>(Expression<Func<T, TProp>> propertySelector, Operator comparison, TProp value)
{
var args = propertySelector.Parameters;
Expression body;
switch (comparison)
{
case Operator.Less:
body = Expression.LessThan(
propertySelector.Body,
Expression.Constant(value)
);
break;
// etc. etc.
default:
body = Expression.Equal(
propertySelector.Body,
Expression.Constant(value)
);
break;
}
return Expression.Lambda<Func<T, bool>>(body, args);
}
Usage: Unit Test
I used a simple "Tile" class that you can see the properties of in their initializers.
Downfall #2: If you use that PredicateBuilder function on a type that doesn't implement some of the operators it will throw an exception. (e.g. string and '>=', '<', etc).
[TestMethod]
public void PredicateBuilderBasicTest()
{
// ASSEMBLE: Create a list of objects for filtering.
IEnumerable<Tile> tiles = new List<Tile>()
{
new Tile() { Name = "Solid", Type = "Grass", X = 0, Y = 0, IsBroken = false },
new Tile() { Name = "Liquid", Type = "Water", X = 1, Y = 0, IsBroken = false },
new Tile() { Name = "Broken", Type = "Grass", X = 0, Y = 1, IsBroken = true },
new Tile() { Name = "Liquid", Type = "Water", X = 1, Y = 1, IsBroken = false },
new Tile() { Name = "Solid", Type = "Grass", X = 0, Y = 2, IsBroken = false },
new Tile() { Name = "Liquid", Type = "Water", X = 2, Y = 0, IsBroken = false },
new Tile() { Name = "Liquid", Type = "Water", X = 2, Y = 1, IsBroken = false },
new Tile() { Name = "Solid", Type = "Dirt", X = 1, Y = 2, IsBroken = false },
new Tile() { Name = "Broken", Type = "Dirt", X = 2, Y = 2, IsBroken = true }
};
var comparison = Operator.Less;
var value = 2;
// ACT: All tiles where their X is less than 2.
var expression = PredicateBuilder<Tile, int>(x => x.X, comparison, value);
var predicate = expression.Compile();
// ASSERT: Since there are 9 tiles and 3 with X >= 2. Then there should be 6 tiles after the predicate.
Assert.AreEqual<int>(6, tiles.Count(predicate));
}
As you could see, you can just reduce that switch to something like:
var predicate = PredicateBuilder<T, TProp>(a => a.NUMERIC_VALUE, enumOperator, value).Compile();
tmpResult = tmpResult.Where(predicate);
But, I still don't recommend doing this.
So just to re-iterate reasons not to do this:
- More code (just less in the place you want).
- Increased Complexity (making it harder to maintain when something goes wrong).
- Decreased performance (This might not matter than much and probably will be negligible, but still).
- Increased room for error (when someone does something like
PredicateBuildering<T, string>(x => x.SomeString, Operator.Less, "Testing");
and throws an exception because strings don't implement the <
operator).
Hopefully this will make sense to you. I hope this information is useful.