2011-11-18 21 views
5

Próbuję utworzyć ogólną metodę, która zwróci predykat w celu znalezienia elementów w dokumencie XML.Czy istnieje sposób wykonywania tego rodzaju rzutowania w predykacie C#

Zasadniczo coś takiego:

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

Jedyną rzeczą jest to, że nie kompiluje. Problem polega na (T)x.Attribute(criterion.PropertyName) części, gdzie kompilator wskazuje:

Nie można rzucać wyrażenia typu „System.Xml.Linq.XAttribute” do rodzaju

Obecnie mam dwie metody „T”, które są identyczne, z wyjątkiem tego, że jeden rzutuje na podwójny, a drugi na dziesiętny. Naprawdę chciałbym nie mieć tego rodzaju duplikacji.

+0

co jest 'Criterion'? –

+0

@ DanielA.White: Jest niestandardową klasą zawierającą dane dla wyrażenia boolowskiego. Nazwa_właściwości jest ciągiem, który reprezentuje nazwę atrybutu w węźle xml, do którego pasuję, a właściwość PropertyValue jest obiektem o wartości, której szukam. –

Odpowiedz

1

XAttribute Class definiuje kilka conversion operators. Jednak podczas rzutowania na ogólny typ parametru T operatory te nie są brane pod uwagę.

Co można zrobić, to zbudować wyrażenia lambda w czasie wykonywania w sposób następujący:

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

Zastosowanie:

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

To prawie zadziałało. Dla typu łańcuchowego działało dobrze, ale dla podwójnego nie powiodło się na prawym var = Expression.Constant (kryterium.PropertyValue, typeof (T)) z ArgumentException "Typy argumentów nie pasują". –

+0

W tym przypadku 'T' nie jest zgodne z typem wartości w' kryterium.PropertyValue'. Dodałem kilka roboczych przykładów. – dtb

1

Nie ma żadnego implicit or explicit conversions dla dowolnego typu T. Jedynymi konwersje dozwolony od XAttribute do innego typu są jawne i do tych typów:

Będziesz musiał utworzyć przeciążenia, które przyjmą jeden z powyższych typów i ograniczyć połączenia do jednego z nich.

0

Co to jest T? An XAttribute nie można przekonwertować na niego, chyba że jest jakiś rodzaj ograniczenia na generic. W każdym razie prawdopodobnie chcesz uzyskać Attribute().Value, który jest ciągiem znaków. Następnie możesz zrobić porównanie. Jaki jest typ criterion.PropertyValue?

Jeśli twój XML zawiera prymitywy takie jak liczby, nie możesz po prostu rzucić łańcucha prosto na liczbę. Musisz użyć metod takich jak double.TryParse(). Niestety, nie ma możliwości, bym ograniczył rodzajowy do metody TryParse. Gdyby tak było, byłbyś w stanie powiedzieć T.TryParse. Ale nie ma sposobu, więc nie możesz. Generics prawdopodobnie ci w tym nie pomoże.

+0

Klasa XAttribute definiuje kilka [operatorów konwersji] (http://msdn.microsoft.com/en-us/library/ff986944.aspx). – dtb

+0

@dtb Wow, który jest niezwykle przydatny. Dzięki. Niestety, nie wydaje się, że pomoże to OP. – Tesserex

+0

@Tesserex: Właściwie to robi. Istnieją wyraźne rzutowania zdefiniowane dla innych typów, nie można rzutować na dowolny typ. Twoja sugestia po prostu omija kod, który jest w "XAttribute" już, który obsługuje formatowanie specyficzne dla XML dla niektórych typów i prawdopodobnie złamie. – casperOne

-1

Dodaj XAttribute constrait na metody rodzajowe:

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

-1 Problem polega na tym, że 'XAttribute' nie ma jawnej konwersji na dowolny typ' T'.Powyższe nie rozwiąże problemu. – casperOne

+0

@RedHat: To nie jest dokładnie intencja. Chodzi o to, że predykat jest podobny do x => x.Attribute ("documentversion")> 8 lub w innym przypadku x => x.Attribute ("documentid") == "DOC-5X" lub event x => x.Attribute ("documentprice") <= "85,99". –

1

Jeśli po prostu zastąpić odlewane do T z odlewów do dynamic, to wtedy by działało. Nie czułbym się źle z powodu wyrzucenia bezpieczeństwa typu tutaj, ponieważ prawdopodobnie nie możesz zagwarantować, że rzeczy w atrybutach XML są w każdym razie właściwym typem - więc bezpieczeństwo typu było iluzją przez cały czas.

+0

Mimo że była to bardzo obiecująca i prosta opcja, nie zadziałała. Działa zetknięcie dla opcji Równe i Nie równe, ale dla innych istnieje zawsze wyjątek, którego ten ciąg (wartość Atrybutu) nie może być porównywany z podwójnym (wartość Wartość właściwości). –

0

Nie można porównać dwóch T s przy użyciu ==, ale powinno działać object.Equals().

Aby wykonać konwersję, można użyć Convert.ChangeType():

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

Problem polega na tym, że XML wykorzystuje różne zasady konwersji w niektórych przypadkach (np Double.PositiveInfinity jest reprezentowany jako INF).

Aby rozwiązać ten problem, można użyć the XmlConvert class, który jest używany wewnętrznie przez operatorów konwersji. Oprócz tego, że nie ma „rodzajowe” metody jak Convert.ChangeType(), więc trzeba by tworzyć własne:

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

To naprawdę nie atakuje problemu dla rzeczy, które nie są kontrolą równości, jak operatory porównania. – mquander

+0

Masz rację. Nie myślałem o tym. Chociaż myślę, że wiele z tych typów implementuje nietypowe 'IComparable', więc to by się tym zająć. – svick

Powiązane problemy