2011-11-18 8 views
5

? Estoy intentando crear un método genérico que devuelva un predicado para encontrar elementos en un documento XML.¿Hay alguna manera de hacer este tipo de conversión en un predicado C#

Básicamente algo como esto:

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

Lo único es que esto no se compila. El problema está en la parte (T)x.Attribute(criterion.PropertyName) donde el compilador indica:

no puede lanzar expresión de tipo 'System.Xml.Linq.XAttribute' para escribir

Actualmente tengo dos métodos 'T' que son idénticos, excepto que uno arroja al doble y el otro a decimal. Realmente me gustaría no tener ese tipo de duplicación.

+0

lo que es 'Criterion'? –

+0

@ DanielA.White: es una clase personalizada que contiene los datos para la expresión booleana. PropertyName es una cadena que representa el nombre del atributo en el nodo xml con el que estoy haciendo una coincidencia y PropertyValue es un objeto con el valor que estoy buscando. –

Respuesta

1

El XAttribute Class define varias conversion operators. Sin embargo, al convertir a un parámetro de tipo genérico T, estos operadores no se tienen en cuenta.

Lo que puede hacer es construir la expresión lambda en tiempo de ejecución de la siguiente manera:

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

Uso:

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

Casi funcionó. Para un tipo de cadena, funcionó bien pero, para el doble, falló en el var right = Expression.Constant (criterion.PropertyValue, typeof (T)) con una ArgumentException "Los tipos de argumento no coinciden". –

+0

En este caso, 'T' no coincide con el tipo del valor en' criterion.PropertyValue'. He agregado algunos ejemplos de trabajo. – dtb

1

No hay implicit or explicit conversions en un tipo arbitrario T. Las únicas conversiones permitidas de XAttribute a otro tipo son explícitas y para estos tipos:

Deberá crear sobrecargas que tengan uno de los tipos anteriores y limitar las llamadas a una de ellas.

0

¿Qué se supone que es T? No se puede convertir un XAttribute a menos que haya algún tipo de restricción en el genérico. En cualquier caso, es probable que desee obtener Attribute().Value, que es una cadena. Entonces puedes hacer una comparación. ¿Cuál es el tipo de criterion.PropertyValue?

Si su XML contiene primitivas como números, no puede simplemente lanzar una cadena directamente a un número. Debe usar métodos como double.TryParse(). Lamentablemente, no hay manera de que sepa restringir un genérico para tener un método TryParse. Si hubiera, podría decir T.TryParse. Pero no hay forma, entonces no puedes. Los genéricos probablemente no te ayudarán con esto.

+0

La clase XAttribute define varios [operadores de conversión] (http://msdn.microsoft.com/en-us/library/ff986944.aspx). – dtb

+0

@dtb Wow eso es extremadamente útil. Gracias. Desafortunadamente, no parece ayudar al OP aquí. – Tesserex

+0

@Tesserex: en realidad, lo hace. Existen moldes explícitos definidos para otros tipos, no se puede convertir a un tipo arbitrario. Su sugerencia simplemente eludiría el código que está en 'XAttribute' ya que maneja el formato XML específico para ciertos tipos y posiblemente se rompa. – casperOne

-1

Añadir XAttribute constrait el método genérico:

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

-1 El problema es que 'XAttribute' no tiene conversiones explícitas a un tipo arbitrario' T'.Lo anterior no resolverá el problema – casperOne

+0

@RedHat: Esta no es exactamente la intención. La idea es que el predicado sea algo así como x => x.Attribute ("documentversion")> 8 o en un caso diferente x => x.Attribute ("documentid") == "DOC-5X" o evento x => x.Attribute ("documentprice") <= "85.99". –

1

Si acaba de cambiar sus moldes a T con los moldes a dynamic, se funcionaría entonces. No me sentiría mal por tirar la seguridad del tipo aquí, ya que probablemente no puedas asegurarte de que las cosas en los atributos XML sean del tipo correcto de todos modos, así que la seguridad del tipo fue una ilusión todo el tiempo.

+0

Aunque esta fue una opción muy prometedora y simple, no funcionó. Trabaja por las opciones Igual y No igual, pero para los demás siempre hay una excepción de que la cadena (el valor de Atributo) no se puede comparar con el doble (valor de Valor de propiedad). –

0

No puede comparar dos T s usando ==, pero object.Equals() debería funcionar.

para hacer la conversión, usted podría utilizar Convert.ChangeType():

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

El problema con esto es que XML utiliza diferentes reglas para las conversiones en algunos casos (por ejemplo Double.PositiveInfinity se representa como INF).

Para resolver esto, puede usar the XmlConvert class, que los operadores de conversión utilizan internamente. Excepto que no tienen un método “genérico” como Convert.ChangeType(), por lo que tendría que crear su propia:

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

Esto realmente no ataca el problema para cosas que no son verificaciones de igualdad, como los operadores de comparación. – mquander

+0

Tienes razón. No pensé en eso. Aunque creo que muchos de los tipos en cuestión implementan el 'IComparable' no genérico, así que eso podría solucionarlo. – svick

Cuestiones relacionadas