Есть ли способ сделать этот тип приведения в предикат C # - PullRequest
5 голосов
/ 19 ноября 2011

Я пытаюсь создать универсальный метод, который будет возвращать предикат для поиска элементов в документе XML.

В основном как-то так:

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.");
    }
} 

Единственное, что это не компилируется. Проблема в части (T)x.Attribute(criterion.PropertyName), где компилятор указывает:

Невозможно привести выражение типа 'System.Xml.Linq.XAttribute' к типу. 'Т'

В настоящее время у меня есть два идентичных метода, за исключением того, что один приводит к удвоению, а другой к десятичному. Мне бы очень хотелось, чтобы такого дублирования не было.

Ответы [ 6 ]

1 голос
/ 19 ноября 2011

Если вы просто замените ваши приведения на T на приведения к dynamic, тогда это сработает. Я не буду расстраиваться из-за отказа от безопасности типов здесь, так как вы, вероятно, не можете гарантировать, что содержимое атрибутов XML в любом случае является правильным типом, поэтому безопасность типов была иллюзией с самого начала.

1 голос
/ 19 ноября 2011

Класс XAttribute определяет несколько операторов преобразования .Однако при приведении к параметру универсального типа T эти операторы не учитываются.

Что вы можете сделать, это построить лямбда-выражение во время выполнения следующим образом:

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();
}

Использование:

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));
1 голос
/ 19 ноября 2011

Нет явных или неявных преобразований в произвольный тип T. Единственные допустимые преобразования из XAttribute в другой тип являются явными и для этих типов:

Вам придется создавать перегрузки, которые принимают один из перечисленных выше типов, и ограничивать количество вызовов одним из них.

0 голосов
/ 19 ноября 2011

Вы не можете сравнивать два T с использованием ==, но object.Equals() должно работать.

Чтобы выполнить преобразование, вы можете использовать Convert.ChangeType():

case CriteriaOperator.Equal:
    return x => object.Equals(
        Convert.ChangeType(x.Attribute(criterion.PropertyName).Value, typeof(T)),
        criterion.PropertyValue);

Проблема в том, что в некоторых случаях XML использует разные правила для преобразования (например, Double.PositiveInfinity представляется как INF).

Чтобы решить эту проблему, вы можете использовать XmlConvert класс , который используется внутри операторов преобразования.За исключением того, что у него нет «универсального» метода, такого как Convert.ChangeType(), поэтому вам придется создать свой собственный:

private static object Convert(string value, Type targetType)
{
    if (targetType == typeof(double))
        return XmlConvert.ToDouble(value);

    …

    throw new ArgumentException();
}

…

case CriteriaOperator.Equal:
    return x => object.Equals(
        Convert(x.Attribute(criterion.PropertyName).Value, typeof(T)),
        criterion.PropertyValue);
0 голосов
/ 19 ноября 2011

Добавить XAttribute на общий метод:

 GetPredicate<T>(Criterion criterion) where T : XAttribute
0 голосов
/ 19 ноября 2011

Что должно быть T?XAttribute не может быть преобразован в него, если нет какого-либо ограничения на общий тип.В любом случае вы, вероятно, захотите получить Attribute().Value, что является строкой.Тогда вы можете сделать сравнение.Каков тип criterion.PropertyValue?

Если ваш XML содержит примитивы, такие как числа, вы не можете просто привести строку прямо к числу.Вам нужно использовать такие методы, как double.TryParse().К сожалению, я не знаю, как ограничить универсальный метод TryParse.Если бы они были, вы могли бы сказать T.TryParse.Но нет пути, поэтому ты не можешь.Дженерики, вероятно, не помогут вам в этом.

...