user14406060
user14406060

Reputation:

Using LINQ Any in Expression

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

Answers (1)

Evk
Evk

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

Related Questions