2011-11-18 12 views
5

Sto provando a creare un metodo generico che restituirà un predicato per trovare elementi in un documento XML.C'è un modo per fare questo tipo di casting in un predicato C#

Fondamentalmente qualcosa di simile:

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

L'unica cosa è che questo non può essere compilato. Il problema è in parte (T)x.Attribute(criterion.PropertyName) in cui il compilatore indica:

non può lanciare l'espressione di tipo 'System.Xml.Linq.XAttribute' di digitare 'T'

Attualmente ho due metodi che sono identici tranne che uno getta al doppio e l'altro al decimale. Mi piacerebbe davvero non avere quel tipo di duplicazione.

+0

ciò che è 'Criterion'? –

+0

@ DanielA.White: è una classe personalizzata che contiene i dati per l'espressione booleana. PropertyName è una stringa che rappresenta il nome dell'attributo nel nodo xml con cui sto confrontando e PropertyValue è un oggetto con il valore che sto cercando. –

risposta

1

Il XAttribute Class definisce diversi conversion operators. Tuttavia, quando si esegue il casting su un parametro di tipo generico T, questi operatori non vengono presi in considerazione.

Che cosa si può fare è costruire l'espressione lambda a runtime come segue:

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

Ha quasi funzionato. Per un tipo di stringa ha funzionato bene, ma per il doppio non è riuscito sulla destra var = Expression.Constant (criterion.PropertyValue, typeof (T)) con un ArgumentException "I tipi di argomenti non corrispondono". –

+0

In questo caso, 'T' non corrisponde al tipo di valore in' criterion.PropertyValue'. Ho aggiunto alcuni esempi di lavoro. – dtb

1

Non ci sono implicit or explicit conversions in un tipo arbitrario T. Le uniche conversioni consentite XAttribute ad un altro tipo sono esplicita e per questi tipi:

Dovrai creare sovraccarichi che richiedono uno dei tipi sopra riportati e limitare le chiamate a uno di questi.

0

Che cosa dovrebbe essere T? Un XAttribute non può essere convertito ad esso a meno che non ci sia un qualche tipo di vincolo sul generico. In ogni caso, probabilmente vorrai ottenere Attribute().Value, che è una stringa. Quindi puoi fare un confronto. Qual è il tipo di criterion.PropertyValue?

Se il codice XML contiene numeri primitivi, non è possibile eseguire il cast di una stringa direttamente su un numero. È necessario utilizzare metodi come double.TryParse(). Sfortunatamente non c'è modo di sapere per vincolare un generico ad avere un metodo TryParse. Se ci fosse, si potrebbe dire T.TryParse. Ma non c'è modo, quindi non puoi. Generics probabilmente non ti aiuterà con questo.

+0

La classe XAttribute definisce diversi [operatori di conversione] (http://msdn.microsoft.com/en-us/library/ff986944.aspx). – dtb

+0

@dtb Wow è estremamente utile. Grazie. Sfortunatamente non sembra aiutare l'OP qui. – Tesserex

+0

@Tesserex: In realtà, lo fa. Ci sono cast espliciti definiti per altri tipi, non puoi castare su un tipo arbitrario. Il tuo suggerimento aggirerebbe solo il codice contenuto in 'XAttribute' che gestisce la formattazione specifica per XML per alcuni tipi e potrebbe interrompersi. – casperOne

-1

Aggiungi XAttribute constrait sul metodo generico:

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

-1 Il problema è che 'XAttribute' non ha conversioni esplicite su un tipo arbitrario' T'.Quanto sopra non risolverà il problema – casperOne

+0

@RedHat: Questo non è esattamente l'intento. L'idea è che il predicato sia qualcosa come x => x.Attribute ("documentversion")> 8 o in un caso diverso x => x.Attribute ("documentid") == "DOC-5X" o evento x => x.Attribute ("documentprice") <= "85.99". –

1

Se basta sostituire le calchi di T con calchi di dynamic, si funzionerebbe allora Non mi farebbe male buttare via la sicurezza del tipo qui dato che probabilmente non è possibile garantire che il contenuto degli attributi XML sia comunque il tipo giusto, quindi digitare safety è stata un'illusione fin dall'inizio.

+0

Sebbene questa fosse un'opzione molto promettente e semplice, non ha funzionato. Funziona alla griglia per le opzioni Uguale e Non uguale ma, per gli altri, c'è sempre un'eccezione che la stringa (il valore dell'attributo) non può essere confrontata con il doppio (valore PropertyValue). –

0

Non è possibile confrontare due T s utilizzando ==, ma il object.Equals() dovrebbe funzionare.

per eseguire la conversione, è possibile utilizzare Convert.ChangeType():

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

Il problema di questo è che XML utilizza regole diverse per le conversioni in alcuni casi (ad esempio Double.PositiveInfinity è rappresentato come INF).

Per risolvere questo problema, è possibile utilizzare the XmlConvert class, che viene utilizzato internamente dagli operatori di conversione. Solo che non dispone di un metodo di “generico” come Convert.ChangeType(), così si dovrà creare il proprio:

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

Questo in realtà non attacca il problema per cose che non sono controlli di uguaglianza, come gli operatori di confronto. – mquander

+0

Hai ragione. Non ci ho pensato. Anche se penso che molti dei tipi in questione implementino il non-generico 'IComparable', in modo che si possa occupare di ciò. – svick

Problemi correlati