Reputation:
I have to use Expression from LINQ to evaluate some data. I have created this small test program. The first example works find. It returns TRUE if any string is "abc".
public class ContainProgramClass
{
static void Main(string[] args)
{
ParameterExpression instance = Expression.Parameter(typeof(ContainClass), "item");
var mainClass = new ContainClass { Data = new List<string> { "abc", "def" } };
var expression = mainClass.CreateExpression(instance);
var lambda = Expression.Lambda<Func<ContainClass, bool>>(expression, instance);
Func<ContainClass, bool> f = lambda.Compile();
var result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result true
mainClass.Data = new List<string> { "foo", "bar" };
result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result false
Console.ReadKey();
}
}
public class ContainClass
{
private static readonly MethodInfo MethodContains = typeof(Enumerable).GetMethods(
BindingFlags.Static | BindingFlags.Public)
.Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2);
private static readonly MethodInfo EnumerableCastMethod = typeof(Enumerable).GetMethod("Cast");
private static MethodInfo GenericContainsMethod = MethodContains.MakeGenericMethod(typeof(object));
public List<string> Data { get; set; }
public Expression CreateExpression(Expression instance)
{
string propertyName = nameof(Data);
MemberExpression collectionPropertyAccessor = Expression.Property(instance, propertyName);
MethodCallExpression genericCollectionPropertyAccessor = Expression.Call(null
, EnumerableCastMethod.MakeGenericMethod(new[] { typeof(object) })
, collectionPropertyAccessor);
var distinctValueConstant = Expression.Constant("abc");
var containsExpression = Expression.Call(
ContainClass.GenericContainsMethod,
genericCollectionPropertyAccessor,
distinctValueConstant);
return containsExpression;
}
}
However, I would like for it to return TRUE if any SUBstring is "abc". Like this:
public class AnyProgramClass
{
static void Main(string[] args)
{
ParameterExpression instance = Expression.Parameter(typeof(ContainClass), "item");
var mainClass = new ContainClass { Data = new List<string> { "abcef", "ghi" } };
var expression = mainClass.CreateExpression(instance);
var lambda = Expression.Lambda<Func<ContainClass, bool>>(expression, instance);
Func<ContainClass, bool> f = lambda.Compile();
var result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result true
mainClass.Data = new List<string> { "abc", "bar" };
result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result true
mainClass.Data = new List<string> { "foo", "bar" };
result = f(mainClass);
Console.WriteLine($"Result: {result}"); // Result false
Console.ReadKey();
}
}
public class MainClass1
{
private static readonly MethodInfo MethodContains = typeof(Enumerable).GetMethods(
BindingFlags.Static | BindingFlags.Public)
.Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2);
private static readonly MethodInfo MethodAny = typeof(Enumerable).GetMethods(
BindingFlags.Static | BindingFlags.Public)
.Single(m => m.Name == nameof(Enumerable.Any) && m.GetParameters().Length == 2);
private static readonly MethodInfo EnumerableCastMethod = typeof(Enumerable).GetMethod("Cast");
private static MethodInfo GenericContainsMethod = MethodContains.MakeGenericMethod(typeof(object));
private static MethodInfo GenericAnyMethod = MethodAny.MakeGenericMethod(typeof(object));
public List<string> Data { get; set; }
public Expression CreateExpression(Expression instance)
{
string propertyName = nameof(Data);
MemberExpression collectionPropertyAccessor = Expression.Property(instance, propertyName);
// would something like
// listOfStrings.Any(str => str.Contains("abc"))
return anyExpression;
}
}
I really have no idea on how to implement that Any(....). I usually get lost in the data types. Hopes some Expression-expert can lent me hand with this :)
Upvotes: 1
Views: 610
Reputation: 101473
You can do it for example like this:
string propertyName = nameof(Data);
MemberExpression collectionPropertyAccessor = Expression.Property(instance, propertyName);
var anyMethod = MethodAny.MakeGenericMethod(typeof(string));
Expression<Func<string, bool>> contains = x => x.Contains("abc");
return Expression.Call(anyMethod, collectionPropertyAccessor, contains);
Here you let compiler build expression you are passing to Any(..)
call. If that doesn't suite your needs, you can build that expression manually:
string propertyName = nameof(Data);
MemberExpression collectionPropertyAccessor = Expression.Property(instance, propertyName);
var anyMethod = MethodAny.MakeGenericMethod(typeof(string));
// find string.Contains(string)
var containsMethod = typeof(string).GetMethods(BindingFlags.Instance | BindingFlags.Public).Single(c => c.Name == "Contains" && c.GetParameters().Length == 1 && c.GetParameters()[0].ParameterType == typeof(string));
// new parameter for sub-expression you pass to Any
ParameterExpression x = Expression.Parameter(typeof(string), "x");
// (string x) => x.Contains("abc") expression
Expression<Func<string, bool>> contains = Expression.Lambda<Func<string, bool>>(
Expression.Call(x, containsMethod, Expression.Constant("abc")), x);
return Expression.Call(anyMethod, collectionPropertyAccessor, contains);
Upvotes: 1