是否有一种方法可以在c#谓词中实现这种类型的强制转换?

本文关键字:种类 实现 类型 转换 谓词 一种 方法 是否 | 更新日期: 2023-09-27 18:15:04

我正在尝试创建一个通用方法,该方法将返回谓词以查找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'为类型"T"

目前我有两个相同的方法,除了一个强制转换为double,另一个强制转换为decimal。我真的不希望有这样的重复。

是否有一种方法可以在c#谓词中实现这种类型的强制转换?

XAttribute class定义了几个转换操作符。但是,当转换为泛型类型参数T时,不考虑这些操作符。

你可以做的是在运行时构造lambda表达式,如下所示:

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

没有到任意类型T的隐式或显式转换。唯一允许的从XAttribute到其他类型的转换是显式的,并且是以下类型:

  • Boolean
  • Nullable<Boolean>
  • DateTime
  • Nullable<DateTime>
  • DateTimeOffset
  • Nullable<DateTimeOffset>
  • Decimal
  • Nullable<Decimal>
  • Double
  • Nullable<Double>
  • Guid
  • Nullable<Guid>
  • Int32
  • Nullable<Int32>
  • Int64
  • Nullable<Int64>
  • Single
  • Nullable<Single>
  • String
  • TimeSpan
  • Nullable<TimeSpan>
  • UInt32
  • Nullable<UInt32>
  • UInt64
  • Nullable<UInt64>

您必须创建采用上述类型之一的重载,并将调用限制为其中之一。

如果您只是将对T的强制转换替换为对dynamic的强制转换,那么它将工作。我不会因为在这里抛弃类型安全而感到遗憾,因为您可能无法确保XML属性中的内容是正确的类型——所以类型安全一直是一种错觉。

T应该是什么?除非对泛型有某种约束,否则XAttribute不能转换为它。在任何情况下,您可能希望获得Attribute().Value,这是一个字符串。然后你可以做一个比较。criterion.PropertyValue是什么类型的?

如果XML包含像数字这样的原语,则不能直接将字符串强制转换为数字。您需要使用double.TryParse()之类的方法。不幸的是,我知道没有办法约束一个泛型有一个TryParse方法。如果有的话,你可以说T.TryParse。但是没有办法,所以你不能。泛型可能不会帮到你。

你不能用==来比较两个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);

为泛型方法添加XAttribute约束:

 GetPredicate<T>(Criterion criterion) where T : XAttribute