Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to do this kind of casting in a c# predicate

I'm trying to create a generic method that will return a predicate to find elements in an XML document.

Basically something like this:

private static Func<XElement, bool> GetPredicate<T>(Criterion criterion)
{
    switch (criterion.CriteriaOperator)
    {
        case CriteriaOperator.Equal:
            return x => (T)x.Attribute(criterion.PropertyName) == 
                (T)(criterion.PropertyValue);
        case CriteriaOperator.GreaterThan:
            return x => (T)x.Attribute(criterion.PropertyName) > 
                (T)(criterion.PropertyValue);
        case CriteriaOperator.GreaterThanOrEqual:
            return x => (T)x.Attribute(criterion.PropertyName) >= 
                (T)(criterion.PropertyValue);
        case CriteriaOperator.LessThan:
            return x => (T)x.Attribute(criterion.PropertyName) < 
                (T)(criterion.PropertyValue);
        case CriteriaOperator.LessThanOrEqual:
            return x => (T)x.Attribute(criterion.PropertyName) <= 
                (T)(criterion.PropertyValue);
        case CriteriaOperator.NotEqual:
            return x => (T)x.Attribute(criterion.PropertyName) != 
                (T)(criterion.PropertyValue);
        default:
            throw new ArgumentException("Criteria Operator not supported.");
    }
} 

Only thing is that this doesn't compile. The problem is on the (T)x.Attribute(criterion.PropertyName) part where the compiler indicates:

Cannot cast expression of type 'System.Xml.Linq.XAttribute' to type 'T'

Currently I have two methods that are identical except that one casts to double and the other one to decimal. I would really like not to have that kind of duplication.

like image 650
Sergio Romero Avatar asked Nov 05 '22 10:11

Sergio Romero


1 Answers

The XAttribute Class defines several conversion operators. However,when casting to a generic type parameter T, these operators are not taken into consideration.

What you can do is construct the lambda expression at runtime as follows:

private static Func<XElement, bool> GetPredicate<T>(Criterion criterion)
{
    var arg = Expression.Parameter(typeof(XElement), "arg");
    var name = Expression.Constant((XName)criterion.PropertyName);
    var attr = Expression.Call(arg, "Attribute", null, name);
    var left = Expression.Convert(attr, typeof(T));
    var right = Expression.Constant(criterion.PropertyValue, typeof(T));

    Expression body;

    switch (criterion.CriteriaOperator)
    {
    case CriteriaOperator.Equal:
        body = Expression.Equal(left, right);
        break;
    case CriteriaOperator.GreaterThan:
        body = Expression.GreaterThan(left, right);
        break;
    default:
        throw new ArgumentException("Criteria Operator not supported.");
    }

    return Expression.Lambda<Func<XElement, bool>>(body, arg).Compile();
}

Usage:

var f = GetPredicate<int>(new Criterion("documentversion", CO.GreaterThan, 8));
var g = GetPredicate<string>(new Criterion("documentid", CO.Equal, "DOC-5X"));
var h = GetPredicate<double>(new Criterion("documentprice", CO.Equal, 85.99d));
like image 114
dtb Avatar answered Nov 15 '22 01:11

dtb