-3

The following code is a helper I've created for a system which allows administrators to create their own queries on a database. It returns a lambda expression based on the method and value provided.

I am attempting to find a way to pass parameters to the method call - in this example I am using the StartsWith parameter of String, and attempting to set StringComparison.OrdinalIgnoreCase as a parameter.

There will be others parameters required too, depending on the type of the property specified. I'm hoping that understanding the method of supplying the string comparison property will enable me to add the rest later.

The underlying code works correctly, I just need it to be case-insensitive.

I have used this question as a guide, but the solution does not seem applicable here.

Here is the code:

public static class LambdaExpressionHelper<T> {
    public static Expression<Func<T, bool>> Build(string propertyName, string method, string propertyValue) {
        PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);

        ParameterExpression e = Expression.Parameter(typeof(T), "e");
        MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
        ConstantExpression c = Expression.Constant(propertyValue, m.Type);
        MethodInfo mi = m.Type.GetMethod(method, new Type[] { m.Type }, );

        // The below caused errors
        //object classInstance = Activator.CreateInstance(typeof(T), null);
        //object[] paramArray = new object[] { StringComparison.OrdinalIgnoreCase };
        //mi.Invoke(classInstance, paramArray);         

        Expression call = Expression.Call(m, mi, c);

        Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, e);
        return lambda;
    }
}

// External code:
var lambda = LambdaExpressionHelper<MailingListMember>.Build("EmailAddress", "StartsWith", "RoRy@");

Thanks for any help.

Community
  • 1
  • 1
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339

2 Answers2

2

In the general case this should work:

public static Expression<Func<T, bool>>
Build(string propertyName, string method, params object[] args) 
{
    var propertyInfo = typeof(T).GetProperty(propertyName);
    var e = Expression.Parameter(typeof(T), "e");
    var m = Expression.MakeMemberAccess(e, propertyInfo);

    var mi = m.Type.GetMethod(method, args.Select(a => a.GetType()).ToArray());
    var c = args.Select(a => Expression.Constant(a, a.GetType())).ToArray();

    Expression call = Expression.Call(m, mi, c);

    Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, e);
    return lambda;
}

The idea is that you accept any number of constant arguments, and use their types to select the appropriate overload when calling GetMethod. After that, you create the appropriate number of constant expressions from those arguments to pass to Expression.Call.

So with the above code, you could do:

var l1 = LambdaExpressionHelper<MailingListMember>.Build(
  "EmailAddress", "StartsWith", "RoRy@");
var l2 = LambdaExpressionHelper<MailingListMember>.Build(
  "EmailAddress", "StartsWith", "RoRy@", StringComparison.OrdinalIgnoreCase);

If you also need to get the value StringComparison.OrdinalIgnoreCase from the string "StringComparison.OrdinalIgnoreCase", I would factor this out into a separate method so that the interface of Build can remain generic. Exactly how to do it is covered in Getting Enum value via reflection (and I guess in lots of similar questions as well).

Note: I don't have convenient access to a compiler right now, so please excuse any mistakes.

Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
-1

I'm not sure i understand your question exactly, but hopefully this will help you on the way.

If you are using .NET 4.0 you can use the new keyword "dynamic" for much easier reflection code:

dynamic myDynamicObj = "Hello World!"; // or Activator.CreateInstance...
var doesIndeed = myDynamicObj.StartsWith("Hello", StringComparison.OrdinalIgnoreCase);

This does evaluate at run time so make sure spelling/case etc is correct.

Edit: Assuming you always wanted to call methods with one String-arg returning bool, a possible solution would be to create a delegate type -

delegate bool CompareString(String str);

and than have the second argument of Build as that type:

Build(String .., CompareString cs, String ...)

But this does not work if you need to add extra arguments, as in the second arg of type StringComparison. A if/switch could be the answer there though, i.e if(CompareString is StartsWith)...

Sorry, not at a windows-computer so i can't test further.

  • Thanks for the reply. This may work, however the method being called - in this case `StartsWith` - needs to be set through code, otherwise I would need to write 50+ `switch` cases. – Rory McCrossan Mar 19 '12 at 12:03